Senior 8 min · March 05, 2026

Java Singleton: 5% Requests Routed Wrong (Missing Volatile)

5% requests misrouted: missing volatile caused partial singleton init.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • Singleton Pattern ensures exactly one instance of a class per JVM
  • Four thread-safe implementations: Double-Checked Locking, Bill Pugh Holder, Enum, synchronized method
  • DCL needs volatile to prevent half-baked objects from being published
  • Enum blocks reflection and serialization attacks automatically
  • Production trap: missing volatile in DCL causes silent NPEs under load (1 in 10,000 runs)
  • Biggest mistake: tight coupling by calling getInstance() directly instead of injecting the instance
Plain-English First

Imagine your school has one printer in the library. No matter which classroom you walk from, you all use that same printer — nobody brings a second one in. The Singleton pattern works exactly like that: it guarantees that a class has only ONE instance in your entire program, and every part of the code that asks for it gets handed the exact same object. That's it. One object, shared everywhere, created once.

Every non-trivial Java application has resources that are expensive to create, dangerous to duplicate, or logically absurd to have more than one of — a connection pool, an application-wide config loader, a logging service. If every class that needs the logger creates its own instance, you waste memory, fragment your log output, and risk race conditions. This is the exact problem the Singleton pattern was designed to solve, and it's why you'll find it baked into frameworks like Spring, Hibernate, and the Java standard library itself.

Why the Naive Singleton Breaks Under Multithreading

The most obvious Singleton implementation — a private constructor plus a static getInstance() method — works perfectly in single-threaded code. But the moment two threads call getInstance() simultaneously before the instance is created, both can slip past the null check, and you end up with two separate instances. That defeats the entire purpose.

This is called a race condition, and it's subtle. In production it might only surface under load, making it one of those nasty bugs that's hard to reproduce locally. The code below shows the broken version first so you can see exactly what goes wrong — then we fix it.

The broken version has no synchronization on the critical section where the instance is first created. Thread A reads instance == null as true, gets paused by the scheduler, Thread B also reads instance == null as true and creates the object, then Thread A resumes and creates a SECOND object. Two singletons. Chaos.

BrokenSingleton.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// ❌ BROKEN: This singleton is NOT thread-safe.
// Two threads can both pass the null check before either sets the field.

public class BrokenSingleton {

    // The one instance — starts as null until first requested
    private static BrokenSingleton instance;

    // Private constructor prevents anyone outside this class calling `new BrokenSingleton()`
    private BrokenSingleton() {
        System.out.println("BrokenSingleton created by thread: " + Thread.currentThread().getName());
    }

    // ❌ No synchronization — race condition lives here
    public static BrokenSingleton getInstance() {
        if (instance == null) {               // Thread A and Thread B can BOTH pass this check
            instance = new BrokenSingleton(); // ...and BOTH create an instance
        }
        return instance;
    }

    public static void main(String[] args) throws InterruptedException {
        // Spin up 5 threads all trying to grab the instance at the same time
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(() -> {
                BrokenSingleton singleton = BrokenSingleton.getInstance();
                System.out.println("Got instance with hashCode: " + singleton.hashCode());
            });
            thread.start();
        }
    }
}
Output
BrokenSingleton created by thread: Thread-0
BrokenSingleton created by thread: Thread-2
Got instance with hashCode: 1829164700
Got instance with hashCode: 2018699554
Got instance with hashCode: 1829164700
Got instance with hashCode: 1829164700
Got instance with hashCode: 2018699554
// Notice: TWO different hashCodes — two separate instances were created. Singleton is broken.
Watch Out:
The broken singleton will often work fine in local testing because your dev machine processes it fast enough that threads rarely collide. It only blows up under real load. Never ship an unsynchronized singleton in multi-threaded code — not even 'temporarily'.
Production Insight
The naive singleton will pass all unit tests because test threads rarely interleave.
In production under load, race condition frequency increases with thread count and context switches.
Rule: use jcstress or ThreadMXBean to reproduce race conditions in test before they hit production.
Key Takeaway
Unsynchronized null check in getInstance() is the source of the race.
Race probability scales with thread count and context switch frequency.
Never omit synchronization on lazy initialization — even for 'simple' singletons.

Double-Checked Locking: The Production-Ready Singleton

The gold-standard fix is called Double-Checked Locking (DCL). The idea: only synchronize during the brief window when the instance is first being created. Once it exists, reads are unsynchronized and fast.

The trick that makes it work is the volatile keyword on the instance field. Without volatile, the JVM's memory model allows instruction reordering — the JVM could write a half-constructed object to instance before its constructor finishes, and another thread could see a non-null but broken object. volatile forces a full memory barrier, preventing that reordering.

DCL is the approach you'll see in professional codebases. It's performant because synchronization only happens once (during creation), and it's correct because volatile closes the memory-visibility gap. It's also worth knowing the Bill Pugh / Initialization-on-Demand Holder idiom as an elegant alternative — shown at the end of this section.

ThreadSafeSingleton.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// ✅ CORRECT: Double-Checked Locking Singleton — thread-safe and performant
// Real-world use case: Application-wide configuration manager

public class ThreadSafeSingleton {

    // `volatile` is NON-NEGOTIABLE here — it prevents the JVM from publishing
    // a half-constructed object to other threads due to instruction reordering
    private static volatile ThreadSafeSingleton instance;

    private final String configFilePath;
    private final int maxConnections;

    // Private constructor loads config once — expensive operation done only once
    private ThreadSafeSingleton() {
        System.out.println("[CONFIG] Loading configuration... (thread: "
                + Thread.currentThread().getName() + ")");
        this.configFilePath = "/etc/myapp/config.yaml"; // simulate reading from disk
        this.maxConnections = 50;
    }

    public static ThreadSafeSingleton getInstance() {
        // FIRST check (no lock): if instance already exists, return immediately — fast path
        if (instance == null) {

            // Only one thread can enter this block at a time
            synchronized (ThreadSafeSingleton.class) {

                // SECOND check (inside lock): another thread may have created
                // the instance while we were waiting to acquire the lock
                if (instance == null) {
                    instance = new ThreadSafeSingleton();
                }
            }
        }
        return instance; // returns the same object every single time after creation
    }

    public String getConfigFilePath() { return configFilePath; }
    public int getMaxConnections()    { return maxConnections; }

    public static void main(String[] args) throws InterruptedException {
        Runnable task = () -> {
            ThreadSafeSingleton config = ThreadSafeSingleton.getInstance();
            System.out.println(Thread.currentThread().getName()
                    + " -> hashCode: " + config.hashCode()
                    + ", maxConnections: " + config.getMaxConnections());
        };

        // Launch 6 threads simultaneously — only ONE constructor call should happen
        Thread[] threads = new Thread[6];
        for (int i = 0; i < 6; i++) {
            threads[i] = new Thread(task, "Worker-" + i);
        }
        for (Thread t : threads) t.start();
        for (Thread t : threads) t.join(); // wait for all threads to finish
    }
}
Output
[CONFIG] Loading configuration... (thread: Worker-0)
Worker-0 -> hashCode: 1829164700, maxConnections: 50
Worker-1 -> hashCode: 1829164700, maxConnections: 50
Worker-2 -> hashCode: 1829164700, maxConnections: 50
Worker-3 -> hashCode: 1829164700, maxConnections: 50
Worker-4 -> hashCode: 1829164700, maxConnections: 50
Worker-5 -> hashCode: 1829164700, maxConnections: 50
// One constructor call. Six threads. Same hashCode every time. That's a correct Singleton.
Pro Tip:
If you don't need lazy initialization (i.e., it's fine to create the instance when the class loads), skip DCL entirely and use the Bill Pugh Holder idiom: a private static inner class that holds the instance. The JVM's class-loading mechanism guarantees thread safety for free, with zero synchronization code. It's simpler, safer, and just as performant.
Production Insight
Without volatile, DCL is broken because the JVM can reorder instance writes before constructor completion.
This is a memory model issue — static analysis won't catch it. Add volatile and validate with memory barrier tests.
Performance: synchronization occurs only once, then fast path returns instance without lock overhead.
Key Takeaway
volatile is non-negotiable in DCL.
The second null check inside synchronized block is the 'double-check'.
Use jcstress to prove correctness under concurrency.

Enum Singleton: The Bulletproof Version You Should Know

Here's a trick that catches a lot of developers off guard: Java's enum type is itself a Singleton. The JVM guarantees that each enum constant is instantiated exactly once, is thread-safe by default, and — crucially — is protected against two attacks that break every other Singleton implementation: serialization and reflection.

With a normal Singleton, a malicious or careless developer can call Constructor.setAccessible(true) via reflection and invoke the private constructor, creating a second instance. Similarly, deserializing a serialized Singleton creates a fresh object. Both of these bypass your getInstance() logic entirely.

The enum approach blocks both attacks. The JVM outright refuses to instantiate enum types via reflection, and the serialization mechanism for enums returns the existing constant rather than a new instance. Joshua Bloch — the author of Effective Java — explicitly recommends this approach. Use it when you're building a Singleton that might be serialized or when security matters.

EnumSingleton.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// ✅ BULLETPROOF: Enum Singleton — reflection-safe, serialization-safe, thread-safe
// Real-world use case: Application event bus or centralized audit logger

public enum EnumSingleton {

    // This is the single instance — the JVM creates it exactly once, guaranteed
    INSTANCE;

    private int eventCount = 0;
    private final String logPrefix = "[AUDIT]";

    // Your actual business methods go here — treat INSTANCE like a regular object
    public void logEvent(String eventDescription) {
        eventCount++;
        System.out.println(logPrefix + " Event #" + eventCount
                + " | " + eventDescription
                + " | Logged by: " + Thread.currentThread().getName());
    }

    public int getTotalEventCount() {
        return eventCount;
    }

    public static void main(String[] args) throws Exception {
        // Normal usage — call via INSTANCE
        EnumSingleton.INSTANCE.logEvent("User login: alice@example.com");
        EnumSingleton.INSTANCE.logEvent("File uploaded: report_q3.pdf");

        // Try to break it with reflection — the JVM will throw an exception
        try {
            java.lang.reflect.Constructor<EnumSingleton> constructor =
                    EnumSingleton.class.getDeclaredConstructor(String.class, int.class);
            constructor.setAccessible(true);
            EnumSingleton hackedInstance = constructor.newInstance("FAKE", 0); // ❌ This will fail
        } catch (Exception e) {
            // JVM protects enum — reflection attack is blocked
            System.out.println("Reflection attack blocked: " + e.getClass().getSimpleName());
        }

        System.out.println("Total events logged: " + EnumSingleton.INSTANCE.getTotalEventCount());
        System.out.println("Instance hashCode: " + EnumSingleton.INSTANCE.hashCode());
    }
}
Output
[AUDIT] Event #1 | User login: alice@example.com | Logged by: main
[AUDIT] Event #2 | File uploaded: report_q3.pdf | Logged by: main
Reflection attack blocked: IllegalArgumentException
Total events logged: 2
Instance hashCode: 1829164700
Interview Gold:
When an interviewer asks 'What's the best way to implement a Singleton in Java?', saying 'Enum Singleton, per Joshua Bloch's Effective Java Item 3' immediately signals you know the language deeply. Most candidates only know DCL. This answer will make you stand out.
Production Insight
Enum singleton is the only one that automatically survives serialization and reflection attacks.
It's eager initialization, so startup cost may increase — not ideal for heavy initialization that's rarely used.
For lazy initialization combined with security, combine a regular class with readResolve() and a static guard against reflection.
Key Takeaway
Enum singleton is the safest approach per Effective Java.
It is eager-initialized (instantiated at class loading).
Reflection protection is built-in — JVM refuses to instantiate enums via reflection.

Real-World Singletons in Java — Where You're Already Using Them

The Singleton pattern isn't just an academic exercise — you use it constantly without realizing it. Understanding where it already appears in Java helps you recognize when to reach for it in your own code.

Runtime.getRuntime() returns the single JVM Runtime instance. System.out is a static final field — one PrintStream, used everywhere. Spring's ApplicationContext is effectively a Singleton container — every bean marked @Scope("singleton") (the default) is managed as a Singleton by the framework. Hibernate's SessionFactory is designed to be instantiated once per application because creating it is extremely expensive.

The pattern to recognize is this: if creating multiple instances of something would either waste significant resources or produce logically incorrect behavior (imagine two separate config stores with different values), that thing is a Singleton candidate.

When NOT to use it: Singletons make unit testing harder because they carry state across tests. They're also a form of global state, which creates hidden coupling between classes. Use dependency injection to pass the single instance around rather than having classes call getInstance() directly — that way you can swap in a mock during testing.

RealWorldSingletonDemo.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// Demonstrating real Singletons already in the Java standard library

public class RealWorldSingletonDemo {

    public static void main(String[] args) {

        // --- 1. Runtime Singleton ---
        // Runtime.getRuntime() always returns the same Runtime object
        Runtime jvmRuntime = Runtime.getRuntime();
        System.out.println("Available processors: " + jvmRuntime.availableProcessors());
        System.out.println("Max JVM memory (bytes): " + jvmRuntime.maxMemory());

        // Calling it again returns the SAME instance — same hashCode
        Runtime anotherReference = Runtime.getRuntime();
        System.out.println("Same Runtime instance? " + (jvmRuntime == anotherReference)); // true

        // --- 2. Bill Pugh Holder Idiom (elegant lazy singleton, no synchronization needed) ---
        // The inner class is only loaded when getInstance() is first called
        DatabaseConnectionPool pool1 = DatabaseConnectionPool.getInstance();
        DatabaseConnectionPool pool2 = DatabaseConnectionPool.getInstance();
        System.out.println("Same pool instance? " + (pool1 == pool2)); // true
        pool1.executeQuery("SELECT * FROM users WHERE active = true");
    }
}

// Bill Pugh Initialization-on-Demand Holder — clean, lazy, thread-safe without volatile or synchronized
class DatabaseConnectionPool {

    private final int poolSize;

    private DatabaseConnectionPool() {
        this.poolSize = 10;
        System.out.println("[DB POOL] Connection pool initialized with " + poolSize + " connections.");
    }

    // The JVM only loads this inner class when getInstance() is first called
    // Class loading in Java is thread-safe — no extra synchronization needed
    private static final class PoolHolder {
        private static final DatabaseConnectionPool POOL_INSTANCE = new DatabaseConnectionPool();
    }

    public static DatabaseConnectionPool getInstance() {
        return PoolHolder.POOL_INSTANCE; // inner class loaded here on first call
    }

    public void executeQuery(String sql) {
        System.out.println("[DB POOL] Executing on pool (size=" + poolSize + "): " + sql);
    }
}
Output
Available processors: 8
Max JVM memory (bytes): 4294967296
Same Runtime instance? true
[DB POOL] Connection pool initialized with 10 connections.
Same pool instance? true
[DB POOL] Executing on pool (size=10): SELECT * FROM users WHERE active = true
The DI Rule:
In Spring or any DI framework, let the container manage your Singleton lifecycle. Don't manually implement getInstance() in a Spring @Component — Spring already guarantees one instance per ApplicationContext. Hand-rolling a Singleton inside a framework that manages them for you is a code smell.
Production Insight
Singletons are global state. In test environments, they cause order-dependent failures.
Inject singletons instead of calling getInstance() to allow mocking.
In Spring, the container manages singletons — don't hand-roll a getInstance() in a @Component.
Key Takeaway
Recognize singleton candidates: expensive resources, logical uniqueness.
Inject singletons for testability.
In DI frameworks, let the container handle lifecycle.

The Bill Pugh Holder Idiom: Lazy Initialization Without Synchronization

The Bill Pugh Initialization-on-Demand Holder idiom is the cleanest way to achieve lazy initialization with full thread safety — no volatile, no synchronized, no double-checked locking. It exploits a guarantee of the JVM: class loading is inherently thread-safe. When getInstance() is called for the first time, the JVM loads the inner Holder class and initializes the static field. That initialization is serialized by the JVM's class-loading mechanism, so no two threads can create the instance.

This pattern is ideal when you need lazy loading (e.g., the singleton initializes a heavy resource) and you don't need reflection or serialization protection. It's simple, readable, and performant — the best choice for most production scenarios where an enum's eager initialization is not acceptable.

BillPughSingleton.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// ✅ Bill Pugh Holder Idiom — lazy, thread-safe, no volatile or synchronized
// Real-world use case: Lazy-loaded event bus or heavy configuration loader

public class BillPughSingleton {

    // The Holder class is loaded only when getInstance() is called
    private static final class SingletonHolder {
        private static final BillPughSingleton INSTANCE = new BillPughSingleton();
    }

    private final String eventBusName;

    private BillPughSingleton() {
        this.eventBusName = "MainEventBus";
        System.out.println("[EVENT BUS] Initializing " + eventBusName);
    }

    public static BillPughSingleton getInstance() {
        return SingletonHolder.INSTANCE; // class loaded here on first call
    }

    public void publishEvent(String event) {
        System.out.println("[EVENT BUS] Publishing: " + event);
    }

    public static void main(String[] args) throws InterruptedException {
        Runnable task = () -> {
            BillPughSingleton bus = BillPughSingleton.getInstance();
            bus.publishEvent("User logged in");
            System.out.println("Bus hashCode: " + bus.hashCode());
        };

        Thread[] threads = new Thread[5];
        for (int i = 0; i < 5; i++) {
            threads[i] = new Thread(task, "Worker-" + i);
        }
        for (Thread t : threads) t.start();
        for (Thread t : threads) t.join();
    }
}
Output
[EVENT BUS] Initializing MainEventBus
[EVENT BUS] Publishing: User logged in
Bus hashCode: 1829164700
[EVENT BUS] Publishing: User logged in
Bus hashCode: 1829164700
... (same hash every time)
How It Works
  • The JVM ensures that a class's static initializer runs exactly once, even if multiple threads trigger class loading concurrently.
  • The inner Holder class is loaded only when getInstance() accesses it — that's the lazy initialization.
  • No volatile on the instance field? Not needed — the JVM's class-loading already provides the memory barrier.
  • The holder class is private — nobody outside the singleton class can reference it, keeping the pattern encapsulated.
Production Insight
Bill Pugh holder is the cleanest lazy singleton but isn't reflection-safe.
If your codebase uses serialization or you need security, use enum instead.
Protect against reflection by throwing an exception in the constructor if instance already exists (track with a static boolean flag).
Key Takeaway
The Bill Pugh holder gives you lazy initialization and thread safety for free.
No volatile, no synchronized, no double-checked locking.
Use it when you need lazy loading and don't need reflection/serialization protection.

UML Class Diagram of Singleton Pattern

The essence of the Singleton pattern is captured in a simple UML class diagram. There is exactly one class, with a private static field of its own type, a private constructor, and a public static method that returns the instance. The diagram below shows the classic structure.

The pattern's elegance lies in its minimalism: no interfaces, no inheritance, just a single class that polices its own instantiation. The private constructor ensures no external class can call new, and the static factory method (getInstance()) controls creation and access.

Production Insight
The UML diagram is a communication tool, not production code. In a real system, you'll often have additional responsibilities in the Singleton class (e.g., connection pooling methods). Keep the getInstance() method lean and focused on instance management.
Key Takeaway
The Singleton UML diagram consists of one class with a private static instance, private constructor, and public static getInstance().

When to Use the Singleton Pattern (Applicability)

The Singleton pattern isn't a hammer for every nail. Use it only when all three of these criteria are met:

  1. Shared Resource: There is a resource that must be shared across the entire application. Examples: a database connection pool, a configuration cache, a thread pool, or a logging service. Having multiple instances would either exhaust resources (too many connections) or cause inconsistent state (different threads see different config values).
  2. Global Access Point: The resource needs to be accessible from many different parts of the codebase without passing it through every constructor. This is a practical concession — in theory, passing dependencies via constructors is cleaner. But for genuinely cross-cutting concerns like logging or metrics, a global access point reduces boilerplate.
  3. Control Instantiation Count: You need strict enforcement that only one instance ever exists. If your design allows multiple instances but just happens to use one, you don't need a Singleton — you need a configurable pool or a factory. Singleton is mandatory when duplication would cause corruption or security issues.

If your use case doesn't satisfy all three, consider alternatives: static methods for utility classes, instance pooling for databases, or dependency injection with scoped beans.

Production Insight
In production, the most common red flag is using Singleton for a resource that could logically have multiple instances (e.g., multiple database schemas). Always ask: 'Would the system break if there were two instances?' If not, don't enforce one.
Key Takeaway
Use Singleton only when you need a shared resource, a global access point, and strict enforcement of single instantiation.

Singleton Implementation Comparison: Synchronized Method, DCL, Bill Pugh, Enum

Choosing the right Singleton implementation depends on your thread-safety, lazy-initialization, and security requirements. The table below summarizes the four most common thread-safe approaches.

ApproachThread-Safe?Lazy Init?Reflection-Safe?Serialization-Safe?Complexity
Synchronized MethodYesYesNoNoLow
Double-Checked LockingYes (with volatile)YesNoNoMedium
Bill Pugh HolderYesYesNoNoLow
Enum SingletonYesNo (eager)YesYesVery Low
  • Synchronized Method: Simplest but has synchronization overhead on every call. Suitable for low-concurrency scenarios.
  • Double-Checked Locking: Fast path after initialization, but requires volatile. Most common in production Java code.
  • Bill Pugh Holder: Cleanest lazy init, no synchronization keywords. Recommended for most applications that don't need serialization/reflection protection.
  • Enum Singleton: The only option that blocks reflection and serialization attacks automatically. Optimal for security-sensitive or serialized singletons, but eager initialization may increase startup time.
Production Insight
The choice of implementation should be documented in the codebase's architecture decision records (ADR). Many teams standardize on Bill Pugh Holder for new code and Enum for anything that touches serialization (e.g., config objects sent over the wire).
Key Takeaway
Enum is safest; Bill Pugh is cleanest for lazy init; DCL is the workhorse; synchronized method is fine for low contention.

Pros and Cons of the Singleton Pattern

The Singleton pattern is a double-edged sword. Here's a balanced look at its advantages and disadvantages.

Pros: - Guarantees a single instance: Prevents duplicate resource allocation and logical inconsistencies. - Saves memory and resources: Heavy objects (config, connection pools) are created once and shared. - Provides a global access point: getInstance() is simple and familiar to all developers. - Lazy initialization: Instance is created only when first requested, improving startup time. - Can be thread-safe: Multiple implementations exist for concurrent environments.

Cons: - Global state: Singletons introduce hidden dependencies and can lead to tight coupling. - Unit testing is harder: State persists across tests; mocking requires extra effort. - Violates the Single Responsibility Principle: The class both manages its lifecycle and performs its business logic. - Concurrency bugs: If not implemented correctly, leads to race conditions and corruption. - Reflection and serialization attacks: Default implementations are vulnerable unless using Enum. - Can mask design problems: Developers may overuse Singletons instead of passing dependencies properly.

Production Insight
In production code, the cons often outweigh the pros. Modern frameworks like Spring manage singletons as scoped beans, decoupling lifecycle management from business logic. Aim to use Singleton as a framework-managed bean rather than hand-rolling the pattern.
Key Takeaway
Singletons guarantee single instances but introduce global state and testing challenges. Use DI to mitigate the downsides.

Relations with Other Design Patterns

The Singleton pattern frequently collaborates with other creational and structural patterns. Here are three key relationships.

Facade Pattern: A Facade class often acts as a single entry point to a complex subsystem. It is commonly implemented as a Singleton to ensure all clients use the same simplified interface. For example, a TransactionFacade that sits in front of multiple microservices might be a Singleton so that all callers share the same cached context.

Flyweight Pattern: Flyweight uses sharing to support large numbers of fine-grained objects efficiently. The Flyweight factory that manages the pool of shared objects is a natural Singleton — you don't want multiple factories creating duplicate Flyweight objects. In graphics rendering, a FontCache Singleton ensures each font is loaded only once.

Abstract Factory Pattern: Abstract Factory provides an interface for creating families of related objects. The concrete factory itself is often a Singleton, because you want exactly one way to produce a consistent family of products. For instance, a GUIFactory for a specific operating system should be instantiated once per application launch.

Understanding these relationships helps you recognize when Singleton is used as a supporting actor rather than the star. In each case, Singleton solves the 'one and only one' requirement for the factory or cache manager.

Production Insight
When using Singleton in conjunction with other patterns, ensure the singleton's scope matches the application's lifecycle. For example, a Singleton Facade in a web application might need to be per-request or per-session, not truly per-JVM. Consider using a DI container's scope settings instead.
Key Takeaway
Singleton is often the backbone of Facade, Flyweight, and Abstract Factory implementations — providing a single control point for sharing and consistency.
● Production incidentPOST-MORTEMseverity: high

Config Singleton Without Volatile Caused Intermittent Cache Corruption

Symptom
Transaction routing service began sending 5% of requests to wrong data centers. Intermittent, non-reproducible in dev. Only happened during peak hours (800+ TPS).
Assumption
Assumed the singleton was thread-safe because getInstance() used double-checked locking pattern.
Root cause
volatile missing on the static instance field. Without it, the JVM's instruction reordering allowed a thread to read a non-null but partially initialized instance. The config cache fields (URLs, credentials) were still null or stale when other threads accessed them.
Fix
Added volatile to the instance field and added a memory barrier. Also added a readResolve() method to protect against serialization. Deployed with JVM flag -XX:+UseG1GC to ensure memory ordering.
Key lesson
  • Always use volatile with DCL — it's not optional, it's load-bearing.
  • Never trust a singleton's thread-safety without explicit memory visibility guarantees.
  • Add assertions or logging to validate instance state on first access in test environments.
Production debug guideHow to diagnose and fix singleton-related production failures4 entries
Symptom · 01
Two different hashCodes returned from getInstance()
Fix
Check if static field is volatile or synchronized. Run a thread contention test with a CountDownLatch to reproduce the race.
Symptom · 02
Intermittent NullPointerException when accessing singleton's fields
Fix
Verify volatile keyword on the instance field. Use -XX:+PrintAssembly to check for memory barrier instructions.
Symptom · 03
Singleton instance created more than once in production logs
Fix
Check for reflection calls or deserialization. Add a static counter in constructor and log it. Switch to Enum singleton if serialization is needed.
Symptom · 04
Unit tests fail because singleton retains state between tests
Fix
Refactor to use dependency injection. Clear singleton state in @BeforeEach by resetting a static flag (if mutable singleton) or use mocking framework.
★ Singleton Failure Quick FixesCommon singleton breakage patterns and how to resolve them immediately
Concurrent threads create two instances (different hashCodes)
Immediate action
Stop the application immediately to prevent data corruption
Commands
grep 'created by thread' application.log | wc -l
javap -c -p YourSingleton.class | grep 'volatile'
Fix now
Add volatile keyword and synchronized block around the null check in getInstance()
Reflection or deserialization creates a second instance+
Immediate action
Identify all callers that might be using serialization or reflection
Commands
grep -r 'readObject' --include='*.java'
grep -r 'setAccessible' --include='*.java'
Fix now
Change to Enum singleton or add readResolve() method that returns getInstance() and throw exception in constructor if instance already exists
Singleton holding stale data after hot-reload in application server+
Immediate action
Restart the application to clear the stale state
Commands
System.out.println(getClass().getClassLoader()) to check classloader
Check JVM arguments for classloader isolation
Fix now
Ensure singleton class is in the root classloader or use a holder class that gets reloaded with the application
Singleton Implementation Comparison
ApproachThread-Safe?Lazy Init?Reflection-Safe?Serialization-Safe?Best For
Naive (no sync)❌ No✅ Yes❌ No❌ NoSingle-threaded demos only
Synchronized method✅ Yes✅ Yes❌ No❌ NoSimple cases, performance not critical
Double-Checked Locking✅ Yes (with volatile)✅ Yes❌ No❌ NoMost production multi-threaded code
Bill Pugh Holder✅ Yes✅ Yes❌ No❌ NoClean, lazy init without volatile
Enum Singleton✅ Yes❌ No (eager)✅ Yes✅ YesSerializable or security-sensitive apps

Key takeaways

1
A Singleton without volatile in a multi-threaded Java app is a ticking time bomb
the bug only appears under load and is nearly impossible to reproduce in dev.
2
The Enum Singleton is the only implementation that's automatically safe against both reflection and serialization attacks
it's Joshua Bloch's explicit recommendation in Effective Java.
3
The Bill Pugh Holder idiom gives you lazy initialization and thread safety for free by exploiting the JVM's own class-loading guarantee
no synchronized, no volatile needed.
4
Singletons are global state in disguise
always inject them rather than calling getInstance() inside business logic, or your code becomes untestable.

Common mistakes to avoid

3 patterns
×

Forgetting `volatile` in Double-Checked Locking

Symptom
Rare, non-reproducible NullPointerExceptions or corrupt state in multi-threaded apps (happens maybe 1 in 10,000 runs). The JVM reorders memory writes and publishes a non-null but half-initialized instance to other threads.
Fix
Always declare the instance field as private static volatile YourClass instance;. Additionally, use a memory barrier test (e.g., jcstress) to verify correctness under concurrency.
×

Breaking the Singleton with Java Serialization

Symptom
After serializing and deserializing your Singleton, deserializedInstance == SingletonClass.getInstance() returns false — you now have two instances.
Fix
Either use the Enum approach (which handles this automatically), or add a readResolve() method to your class: protected Object readResolve() { return getInstance(); }. This tells the serialization mechanism to return the existing instance instead of the newly deserialized one.
×

Calling `getInstance()` directly inside other classes (tight coupling)

Symptom
Unit tests for classes that use the Singleton become nearly impossible to isolate because the real Singleton always initializes (maybe hitting a database, reading a file).
Fix
Accept the Singleton through the constructor or a setter (dependency injection). In tests, pass a mock or stub. In production, pass the real instance. Your classes shouldn't care whether it's a Singleton — that's the Singleton's business, not the caller's.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
Why does Double-Checked Locking require the `volatile` keyword in Java, ...
Q02SENIOR
How would you break a classic (non-enum) Singleton implementation, and h...
Q03SENIOR
What are the downsides of the Singleton pattern in terms of testability ...
Q01 of 03SENIOR

Why does Double-Checked Locking require the `volatile` keyword in Java, and what specific problem does omitting it cause?

ANSWER
Double-Checked Locking (DCL) requires volatile on the instance field to prevent the JVM's instruction reordering. Without volatile, the JVM is allowed to write a reference to the instance field before the constructor has completed. This means another thread can see a non-null reference but access fields that are still default (null/zero). That causes intermittent NullPointerExceptions or data corruption. volatile creates a memory barrier that forces a happens-before relationship: the write to the volatile field and all preceding writes (constructor) become visible to all threads in the correct order. In older Java versions (pre-1.5), DCL was broken even with volatile because the memory model didn't guarantee that volatile writes flush constructor side effects. Since Java 5, it's safe with volatile. Code example of the bug: ``java private static MyClass instance; // missing volatile ` Thread A: instance = new MyClass();` may be reordered to: allocate memory → write reference to instance → call constructor. Thread B sees non-null instance and accesses fields that are still null.
FAQ · 3 QUESTIONS

Frequently Asked Questions

01
Is the Singleton pattern the same as a static class in Java?
02
Does Spring's @Component annotation automatically make a bean a Singleton?
03
Can I use the Singleton pattern in a distributed system across multiple JVMs?
🔥

That's Advanced Java. Mark it forged?

8 min read · try the examples if you haven't

Previous
Design Patterns in Java
8 / 28 · Advanced Java
Next
Factory Pattern in Java