Thread-Safe Singleton Patterns in Java — Enum, Holder, DCL, Eager
Fast recall
Quick revision
Core answer
public enum Singleton { INSTANCE; } — simplest, reflection-proof, serialization-safe, the default recommended by Joshua Bloch in Effective Java. (2) Initialization-on-demand holder — private static nested class holding the instance — lazy, thread-safe via JVM class-initialization locking, no volatile/synchronized needed. (3) Double-checked locking (DCL) with `volatile` — needed only when you must pass constructor arguments or have per-instance logic. (4) Eager static initialization — public static final Singleton INSTANCE = new Singleton(); — simplest but not lazy, wastes memory if never used. For 95% of singletons, enum is the right answer. Pick the holder idiom if enum feels semantically wrong (e.g., the singleton has heavy behavior and you want a class). Pick DCL only when construction requires runtime arguments. Avoid homegrown synchronized getters and plain instance = instance == null ? new... : instance; patterns — both are performance or correctness traps.Whether the candidate knows multiple patterns and can articulate trade-offs (lazy vs eager, serialization, reflection, boilerplate) and pick the right one
Defaulting to DCL for singletons when enum/holder would be simpler. Or using a plain `synchronized` method and losing performance. Or writing non-thread-safe lazy singletons
Cite *Effective Java* Item 3 recommending enum. Explain the serialization/reflection attack surface for class-based singletons. Mention Spring's @Component/@Scope("singleton") as the DI-framework alternative that eliminates the pattern for most application code
Interview-ready guide
Interview answer
Four canonical thread-safe singleton patterns in Java, each with distinct trade-offs.
1. Enum singleton — the default
public enum ConfigService { INSTANCE; ... }- Thread-safe: JVM class-init lock
- Serialization-safe: enum identity preserved automatically
- Reflection-safe:
Constructor.newInstancerefuses enums - Zero boilerplate
- Recommended by Joshua Bloch's Effective Java Item 3
- Downside: no subclassing, harder to refactor out of singleton
2. Initialization-on-demand holder idiom — class-based lazy
private static class Holder {
static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() { return Holder.INSTANCE; }- Holder class loaded only on first
getInstance()— lazy - JVM class-init is thread-safe, no volatile, no synchronized
- Performs identically to DCL
- Use when: class semantics preferred, no constructor args needed
3. Double-checked locking with volatile — for parameterized singletons
private static volatile Singleton instance;
Singleton local = instance;
if (local == null) { synchronized(...) { ... } }- Only when construction needs runtime arguments
- volatile is mandatory — without it, still broken on Java 21
- More code than enum or holder
- Use sparingly
4. Eager static initialization — simplest
public static final Singleton INSTANCE = new Singleton();- Created at class load
- Not lazy; wastes memory if never used
- Use when: construction is cheap and it's always used
The two attacks on class-based singletons
Serialization:
- Default mechanism creates a new instance on deserialize
- Fix: implement
readResolve()returning the existing singleton - Enum immune automatically
Reflection:
Constructor.getDeclaredConstructor().setAccessible(true).newInstance()can bypass private constructors- Fix: throw from the constructor if instance already exists
- Only enum is truly reflection-proof
Decision flow
- Spring/Jakarta EE? →
@Componentwith default scope; don't hand-roll - Need constructor args? → DCL with volatile
- Cheap and always used? → Eager static
- Otherwise → enum
- Want class semantics but also lazy? → Holder idiom
Testing implications
- Singletons pin global state; tests can contaminate each other
- Prefer DI:
@Componentwith Spring context per test, or plain constructor injection - Avoid static
getInstance()accessors in testable business code
Multi-classloader caveat
- In OSGi, app servers, plugin systems: one instance per classloader, not global
- Both class-based and enum are affected
- Rarely matters in typical Spring Boot apps
Recommendation: For application code running in Spring or Jakarta EE, stop hand-rolling singletons — let the container manage scope. For framework or library code that truly needs a global instance, default to enum; it's the only pattern that's simultaneously simple, lazy, serialization-safe, and reflection-safe. Reach for the holder idiom when you specifically want a class instead of an enum (e.g., extending a parent class or implementing complex behavior). Use DCL with volatile only when you need constructor arguments; usually that signals you actually want a small cache, not a singleton. And never write a plain synchronized getInstance() — it's obsolete. Finally, treat any mutable singleton as a risk: the singleton identity is preserved but the mutable state inside it still needs its own thread-safety story.
Thread-Safe Singleton Patterns in Java — Enum, Holder, DCL, Eager
Direct Answer
Four main thread-safe singleton patterns with distinct trade-offs: (1) Enum singleton — public enum Singleton { INSTANCE; } — simplest, reflection-proof, serialization-safe, the default recommended by Joshua Bloch in Effective Java. (2) Initialization-on-demand holder — private static nested class holding the instance — lazy, thread-safe via JVM class-initialization locking, no volatile/synchronized needed. (3) Double-checked locking (DCL) with volatile — needed only when you must pass constructor arguments or have per-instance logic. (4) Eager static initialization — public static final Singleton INSTANCE = new Singleton(); — simplest but not lazy, wastes memory if never used. For 95% of singletons, enum is the right answer. Pick the holder idiom if enum feels semantically wrong (e.g., the singleton has heavy behavior and you want a class). Pick DCL only when construction requires runtime arguments. Avoid homegrown synchronized getters and plain instance = instance == null ? new... : instance; patterns — both are performance or correctness traps.
Common Mistakes & Pitfalls
Defaulting to DCL for singletons when enum/holder would be simpler. Or using a plain synchronized method and losing performance. Or writing non-thread-safe lazy singletons
Check your understanding
Answer these without looking back at the article.
- Why is enum the recommended default for singletons?
- How does the holder idiom achieve thread safety without `volatile` or `synchronized`?
- What does `readResolve()` do and why do class-based singletons need it?
- How does multi-classloader behavior affect 'singleton' semantics?
- In a Spring Boot app, should you ever hand-roll a singleton?
Related interview prep categories
2Looking for a structured roadmap?
Every module on this page is also part of the full Java backend interview track.