Chapter 03 · Article 09 of 55
Singleton Design Pattern
Intent: Ensure a class has only one instance and provide a global point of access to it.
Article outline15 sections on this page+
Overview
Intent: Ensure a class has only one instance and provide a global point of access to it.
The Singleton pattern is a creational design pattern that restricts the instantiation of a class to exactly one object. It provides a well-known access point to that single instance so that all clients share the same object throughout the application lifecycle.
Core Guarantees:
- Single Instance - Only one object of the class exists in the JVM (or runtime environment).
- Global Access Point - A static method (
getInstance()) provides access from anywhere in the codebase. - Controlled Instantiation - The constructor is private; no external code can create a new instance.
The pattern is deceptively simple in concept but notoriously tricky to implement correctly in multithreaded environments - which is precisely why it dominates interview discussions.
Historical Context: Introduced by the Gang of Four (GoF) in 1994, Singleton remains the most frequently asked design pattern in software engineering interviews. Its simplicity invites follow-up questions about concurrency, serialization, and architectural trade-offs that reveal a candidate's depth of understanding.
How it works at a high level:
- Declare the constructor
privateso no external class can callnew. - Create a
private staticfield to hold the single instance. - Expose a
public staticmethod (getInstance()) that creates the instance on first call (lazy) or returns the pre-created instance (eager).
Problem It Solves
Certain resources in a system must be shared across components without creating multiple conflicting instances:
| Problem Domain | Why Singleton Helps |
|---|---|
| Configuration Manager | One source of truth for app settings; avoids inconsistent reads from multiple config objects |
| Database Connection Pool | Pools are expensive to create; multiple pools waste connections and memory |
| Logging Service | All modules should write to the same log destination with consistent formatting |
| Cache Manager | A single shared cache avoids duplication and stale data across layers |
| Hardware Interface Access | Printer spooler, file system handle - physical resources cannot be multiplexed arbitrarily |
| Registry / Service Locator | Central lookup for services must be unique to avoid conflicting registrations |
Without Singleton, developers risk creating multiple instances that compete for shared resources, produce inconsistent state, or waste memory.
When to Use / When NOT to Use
| When to Use | When NOT to Use |
|---|---|
| Exactly one instance is needed system-wide | Object carries mutable state that varies per context |
| Shared access to a costly resource (DB pool, thread pool) | You need multiple instances for testing or multi-tenancy |
| Coordinating actions across the system (event bus, scheduler) | The class has no shared state - a static utility class suffices |
| Replacing global variables with controlled access | You're using it just to avoid passing dependencies (use DI instead) |
| Lazy initialization of expensive objects is desired | The object's lifecycle should be managed by a framework (Spring, Guice) |
| Read-heavy configuration that rarely changes | You need polymorphism or interface-based substitution in tests |
Rule of thumb: If you find yourself needing to mock or replace the Singleton in unit tests, it's a signal that Dependency Injection is a better fit.
Key Concepts & Theory
Lazy vs Eager Initialization
- Eager: Instance is created at class-loading time. Simple, thread-safe by default (JVM guarantees), but wastes memory if never used.
private static final Singleton INSTANCE = new Singleton(); // eager - Lazy: Instance is created on first call to
getInstance(). Saves resources but introduces thread-safety complexity. Preferred when the object is expensive to create and may not be needed in every execution path.
Trade-off Matrix:
| Eager | Lazy | |
|---|---|---|
| Thread Safety | Free (JVM) | Must implement |
| Memory | Allocated at startup | On-demand |
| Startup Time | Slower | Faster |
| Complexity | Trivial | Moderate to High |
Thread Safety
In a multithreaded environment, two threads can simultaneously enter getInstance() before the instance is assigned, creating two objects. Solutions include synchronization, double-checked locking, and leveraging the JVM classloader.
The race condition scenario:
Thread A: checks instance == null → true
Thread B: checks instance == null → true (A hasn't assigned yet)
Thread A: creates instance #1
Thread B: creates instance #2 ← VIOLATION
The volatile keyword in DCL prevents instruction reordering. Without it, the JVM may assign the reference before the constructor completes, exposing a half-initialized object to another thread.
Serialization Concerns
When a Singleton is serialized and then deserialized, the default mechanism creates a new object, breaking the single-instance guarantee. The fix is implementing readResolve() to return the existing instance.
Reflection Attacks
Java Reflection can invoke private constructors, bypassing the Singleton contract. Defensive code in the constructor (throwing an exception if an instance already exists) or using the Enum approach prevents this.
Cloning
If the Singleton class extends Cloneable, calling clone() produces a second instance. Override clone() to throw CloneNotSupportedException.
ASCII Class Diagram
┌─────────────────────────────────────┐
│ <<Singleton>> │
│─────────────────────────────────────│
│ - instance : Singleton [static] │
│─────────────────────────────────────│
│ - Singleton() │ ← private constructor
│ + getInstance() : Singleton [static]│ ← global access point
│ + businessMethod() : void │
└─────────────────────────────────────┘
Client ───uses───► Singleton.getInstance()
│
▼
┌───────────────────┐
│ Single Instance │
│ (heap memory) │
└───────────────────┘
Key relationships:
- The class holds a private static reference to itself.
- The constructor is private - no
new Singleton()from outside. getInstance()is the only creation path.
Pseudocode Implementation
Variant 1: Basic Singleton (Not Thread-Safe)
public class Singleton {
private static Singleton instance;
private Singleton() {} // private constructor
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton(); // race condition here
}
return instance;
}
}
Pros: Simple, lazy initialization. Cons: Broken in multithreaded environments - two threads can create two instances.
Variant 2: Thread-Safe with Synchronized Method
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
Pros: Thread-safe, lazy.
Cons: synchronized on every call - severe performance penalty (100x slower) even after instance exists.
Variant 3: Double-Checked Locking (DCL)
public class Singleton {
private static volatile Singleton instance; // volatile is critical
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // 1st check (no lock)
synchronized (Singleton.class) {
if (instance == null) { // 2nd check (with lock)
instance = new Singleton();
}
}
}
return instance;
}
}
Pros: Thread-safe, lazy, synchronization cost only on first creation.
Cons: Requires volatile (Java 5+); subtle - easy to implement incorrectly. Without volatile, instruction reordering can expose a partially constructed object.
Variant 4: Bill Pugh Singleton (Static Inner Class)
public class Singleton {
private Singleton() {}
private static class Holder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE;
}
}
Pros: Lazy (inner class loaded only on first getInstance() call), thread-safe (JVM classloader guarantees), no synchronization overhead, no volatile needed.
Cons: Cannot pass construction parameters. Still vulnerable to reflection attacks.
This is the recommended approach for most Java applications.
Variant 5: Enum-Based Singleton
public enum Singleton {
INSTANCE;
public void businessMethod() {
// implementation
}
}
// Usage: Singleton.INSTANCE.businessMethod();
Pros: Thread-safe, serialization-safe (JVM handles it), reflection-proof (JVM prevents enum constructor invocation via reflection), concise.
Cons: Cannot extend other classes (enums implicitly extend java.lang.Enum), eager initialization, feels unconventional to some developers.
Joshua Bloch (Effective Java): "A single-element enum type is the best way to implement a singleton."
Variant Comparison Summary
| Variant | Thread-Safe | Lazy | Reflection-Proof | Serialization-Safe | Performance |
|---|---|---|---|---|---|
| Basic | Fast | ||||
| Synchronized | Slow | ||||
| Double-Checked Locking | Fast (after init) | ||||
| Bill Pugh | Fast | ||||
| Enum | (eager) | Fast |
Real-World Examples
1. Logger (java.util.logging.Logger)
All modules obtain the same Logger instance for a given name. Ensures consistent log formatting, destination, and level configuration.
2. Configuration Manager
AppConfig.getInstance().getProperty("db.url")
Reads config once from file/environment, serves all components. Avoids repeated I/O and inconsistent snapshots.
3. Database Connection Pool (HikariCP, C3P0)
A single pool manages a fixed number of connections. Multiple pools would exhaust database connection limits and waste memory.
4. Thread Pool (Executors)
Runtime.getRuntime() - the JVM Runtime is a Singleton. Thread pools are typically singleton-scoped to control concurrency limits globally.
5. Spring Framework Beans
Spring's default scope is singleton - one bean instance per application context. The container manages lifecycle, not the class itself.
6. Java Runtime
Runtime.getRuntime() is a classic JDK Singleton. There's exactly one Runtime per JVM process, providing access to memory info, process execution, and shutdown hooks.
7. Desktop Applications
Print spoolers, clipboard managers, and window managers in GUI frameworks are typically singletons - they coordinate access to a single physical or OS-level resource.
Implementation Pattern in Real Systems
// How HikariCP effectively works as a singleton pool
public class DataSourceProvider {
private static class Holder {
static final HikariDataSource DS = createDataSource();
private static HikariDataSource createDataSource() {
HikariConfig config = new HikariConfig("/hikari.properties");
return new HikariDataSource(config);
}
}
public static DataSource getDataSource() {
return Holder.DS;
}
}
Singleton vs Static Class
| Criteria | Singleton | Static Class |
|---|---|---|
| Instance | Has an actual object on the heap | No instance; just static methods |
| Interface Implementation | Can implement interfaces | Cannot |
| Polymorphism | Supports | Not possible |
| Lazy Loading | Controlled | Loaded at class-load time |
| Serialization | Possible (with care) | Not applicable |
| Testability (Mocking) | Possible via interface extraction | Very difficult to mock |
| State | Can hold instance state | Only static state |
| Inheritance | Can extend classes | Cannot |
| Dependency Injection | Compatible | Not injectable |
| Memory | GC can collect (if reference lost) | Lives until classloader unloads |
Guideline: Use a static class for pure utility functions (e.g., Math, StringUtils). Use Singleton when you need state, polymorphism, or lifecycle control.
Advantages & Disadvantages
| Advantages | Disadvantages |
|---|---|
| Controlled access to sole instance | Violates Single Responsibility Principle (manages own lifecycle + business logic) |
| Reduced memory footprint (one object) | Introduces global state - hidden dependencies |
| Lazy initialization saves startup time | Testability nightmare - hard to mock, hard to reset between tests |
| Can be extended via subclassing (with care) | Tight coupling - clients depend on concrete class |
| Thread-safe access to shared resource | Concurrency complexity in implementation |
| Better than global variables (encapsulated) | Violates Dependency Inversion Principle |
| Compatible with interfaces for abstraction | Can mask poor architecture (God object smell) |
Constraints & Edge Cases
1. Multithreading
Without proper synchronization, race conditions create multiple instances. DCL requires volatile; Bill Pugh and Enum are inherently safe.
2. Serialization / Deserialization
Default deserialization creates a new object via the ObjectInputStream bypassing the constructor entirely. This means even a private constructor won't protect you. Fix:
// Add this method to your Singleton class
protected Object readResolve() {
return getInstance(); // discard deserialized object, return existing
}
Additionally, implement Serializable carefully - all fields should be transient or the deserialized state could conflict with the live instance's state.
3. Reflection Attack
Constructor<Singleton> c = Singleton.class.getDeclaredConstructor();
c.setAccessible(true);
Singleton second = c.newInstance(); // breaks singleton!
Defense strategies:
private Singleton() {
if (instance != null) {
throw new IllegalStateException("Instance already created. Use getInstance().");
}
}
The Enum approach is the only implementation that is completely immune - the JVM itself throws IllegalArgumentException if you attempt to reflectively instantiate an enum.
4. Classloader Issues
Different classloaders can load the same class independently, each creating its own Singleton. Relevant in application servers (EAR with multiple WARs). Solution: Use a common parent classloader or framework-managed singletons.
5. Garbage Collection
If no live reference to the Singleton exists, the GC can collect it (pre-Java 1.2 was aggressive about this). Modern JVMs with static references prevent this, but in OSGi/modular environments, bundle unloading can destroy singletons.
6. Cloning
Override clone():
@Override
protected Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException("Singleton cannot be cloned");
}
Anti-Pattern Discussion
Why Singleton Is Often Considered an Anti-Pattern
- Hidden Dependencies - Code that calls
Singleton.getInstance()has an invisible dependency not expressed in its constructor or method signature. - Global Mutable State - Essentially a glorified global variable; makes reasoning about program state difficult.
- Testing Pain - Cannot easily substitute a mock; tests become order-dependent if the Singleton carries state between tests.
- Concurrency Bugs - Shared mutable state is the root of most concurrency issues.
- Violation of SOLID - Breaks SRP (two responsibilities) and DIP (depends on concrete class).
Alternatives
| Alternative | How It Helps |
|---|---|
| Dependency Injection (DI) | Framework manages single-instance lifecycle; class is unaware it's a singleton |
| Factory + Scope Management | Factory decides how many instances; class remains reusable |
| Service Locator | Centralized registry without hardcoding getInstance() calls |
| Module-level instance | In languages like Python/JS, module itself is a singleton scope |
Modern best practice: Let a DI container (Spring, Guice, Dagger) manage singleton scope. The class itself should be a plain POJO with constructor injection.
The Testing Problem in Detail
// This is UNTESTABLE in isolation:
public class OrderService {
public void placeOrder(Order order) {
DatabasePool.getInstance().getConnection(); // hidden dependency
Logger.getInstance().log("Order placed"); // hidden dependency
}
}
// This is TESTABLE:
public class OrderService {
private final DataSource dataSource;
private final Logger logger;
public OrderService(DataSource ds, Logger logger) { // injected
this.dataSource = ds;
this.logger = logger;
}
}
The first version cannot be unit-tested without a real database and real logger. The second version accepts mocks. This is the fundamental argument against Singleton in modern software - it couples your code to a specific instance strategy rather than an abstraction.
Interview Follow-ups
Q1: How can you break a Singleton in Java?
Answer: There are four ways to break a Singleton:
- Reflection - Access private constructor via
setAccessible(true). - Serialization - Deserialize creates a new object (fix with
readResolve()). - Cloning - If class implements
Cloneable(fix by overridingclone()). - Multiple Classloaders - Each classloader creates its own instance.
The Enum-based Singleton is immune to reflection and serialization attacks.
Q2: How do you make a Singleton thread-safe?
Answer: Multiple approaches in order of recommendation:
- Enum Singleton - JVM guarantees thread safety.
- Bill Pugh (static inner class) - Classloader guarantees; no synchronization cost.
- Double-Checked Locking -
volatile+ synchronized block; lock only on first creation. - Synchronized
getInstance()- Correct but slow; lock on every access. - Eager initialization - Create at class-load time; JVM handles thread safety.
Q3: Singleton vs Spring Singleton Scope - what's the difference?
Answer: A GoF Singleton enforces one instance per JVM via private constructor. A Spring singleton bean is one instance per ApplicationContext - the class itself is a normal class with public constructor. Multiple contexts (e.g., in tests) can create multiple instances. Spring manages the lifecycle externally; the class has no singleton logic.
Hint-Only Questions (for self-practice)
Q4: How would you implement a Singleton in a distributed system (multiple JVMs)? Hints: Think about distributed caches (Redis, Hazelcast), database locks, or ZooKeeper-based leader election. A JVM-level Singleton doesn't help across nodes.
Q5: Can you make a Singleton that accepts constructor parameters?
Hints: Consider a getInstance(Config config) that only uses the parameter on first call. Or use a separate initialize() method. Think about why this is a design smell - if it needs parameters, is it really a Singleton or a managed dependency?
Counter Questions to Ask Interviewer
Use these to demonstrate depth and steer the conversation:
- "Should the Singleton be managed by the class itself or by a DI container?" - Shows awareness of modern practices vs. textbook patterns.
- "Is this Singleton expected to work across multiple classloaders or a single application context?" - Demonstrates understanding of enterprise edge cases.
- "Does the system require the Singleton to survive serialization (e.g., distributed cache, session replication)?" - Shows you think about real deployment scenarios.
- "Are there testability requirements? Should we be able to substitute this in unit tests?" - Signals you value clean architecture over pattern dogma.
- "Is lazy initialization important here, or is eager acceptable given the startup cost?" - Shows you weigh tradeoffs rather than defaulting to the most complex solution.
References & Whitepapers
- Gamma, Helm, Johnson, Vlissides - Design Patterns: Elements of Reusable Object-Oriented Software (GoF, 1994), Chapter 3: Creational Patterns.
- Joshua Bloch - Effective Java, 3rd Edition (2018), Item 3: "Enforce the singleton property with a private constructor or an enum type."
- Brian Goetz - Java Concurrency in Practice (2006), Section 16.2: Safe Publication and Double-Checked Locking.
- Steve Yegge - "Singletons Considered Stupid" (2004) - Critique of Singleton overuse.
- Mark Radford - "Singleton: the anti-pattern" (Overload Journal, 2003).
- Martin Fowler - Inversion of Control Containers and the Dependency Injection Pattern (2004).
- Java Language Specification - §12.4: Initialization of Classes and Interfaces (guarantees thread-safe class loading).
Related Topics
- Factory Method Pattern
- Abstract Factory Pattern
- Builder Pattern
- Dependency Injection & IoC
- Thread Safety & Concurrency
- SOLID Principles
- Object Pool Pattern
Singleton is the simplest pattern to describe and the hardest to justify in modern codebases. Know it deeply for interviews - then use DI in production.