Mastering the Singleton Pattern in Java

The Singleton pattern ensures that a class has only one instance and provides a way to access it from anywhere in the code.

✅ When to use it:

  • You need a single, shared object — like an app-wide settings manager, log writer, or clock.

❌ When not to use it:

  • You need multiple independent instances (e.g., one per user or request).

🔤 Basic Implementation (Eager, Thread-Safe)

public class EagerSingleton {
    private static final EagerSingleton instance = new EagerSingleton();

    private EagerSingleton() {}

    public static EagerSingleton getInstance() {
        return instance;
    }
}

Key points:

  • A private constructor to prevent the creation of additional instances

  • A single private instance that is made accessible in the getInstance method

  • The instance is initialized when the class is loaded, thus, it is eager

🔤 Basic Implementation (Lazy, Not Thread-Safe)

public class LazySingleton {
    private static LazySingleton instance;

    private LazySingleton() {}

    public static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

This lazy implementation instantiates the instance when the getInstance is invoked for the first time, thus it is lazy. This looks fine at first glance, but it fails in multi-threaded environments.

In a multi-threaded program, you can’t control how threads are scheduled. Two threads might call getInstance() at the same time, both see that instance is still null, and both proceed to create a new object. This results in two different instances, breaking the singleton rule.

This issue is called a race condition — and we’ll now look at thread-safe alternatives that avoid it.


Real-World Examples of the Singleton Pattern

Many frameworks use the Singleton pattern to manage shared components.

📦 Spring Framework

By default, Spring creates beans as singletons. When you annotate a class with @Component, Spring instantiates the class once and reuses it throughout the application:

javaCopyEdit@Component
public class MyService {
    // Singleton bean by default
}

🧪 JUnit

JUnit uses a singleton test runner internally to manage and coordinate test execution. This ensures all tests run in a consistent and isolated environment.


Thread-Safe Singleton Implementations

Here are five common ways to implement a thread-safe singleton in Java — each with a short explanation, code sample, and its key advantage.


🔐 1. Eager Initialization (Simple, Thread-Safe)

public class EagerSingleton {
    private static final EagerSingleton instance = new EagerSingleton();
    private EagerSingleton() {}
    public static EagerSingleton getInstance() {
        return instance;
    }
}

✅ Advantage: Very simple and thread-safe because the instance is created when the class loads.
❌ Downside: The instance is created even if it's never used.


🧵 2. Synchronized Method (Safe but Slow)

public class SynchronizedSingleton {
    private static SynchronizedSingleton instance;
    private SynchronizedSingleton() {}
    public static synchronized SynchronizedSingleton getInstance() {
        if (instance == null) {
            instance = new SynchronizedSingleton();
        }
        return instance;
    }
}

✅ Advantage: Easy to write and understand.
❌ Downside: Synchronization happens on every call, which can hurt performance.


🌀 3. Double-Checked Locking (Efficient & Lazy)

public class DoubleCheckedSingleton {
    private static volatile DoubleCheckedSingleton instance;
    private DoubleCheckedSingleton() {}
    public static DoubleCheckedSingleton getInstance() {
        if (instance == null) {
            synchronized (DoubleCheckedSingleton.class) {
                if (instance == null) {
                    instance = new DoubleCheckedSingleton();
                }
            }
        }
        return instance;
    }
}

✅ Advantage: Thread-safe, lazy, and avoids unnecessary locking.
⚠️ Downside: Slightly more complex to write and understand.


📥 4. Static Inner Class (Elegant & Lazy)

public class InnerClassSingleton {
    private InnerClassSingleton() {}
    private static class Holder {
        private static final InnerClassSingleton INSTANCE = new InnerClassSingleton();
    }
    public static InnerClassSingleton getInstance() {
        return Holder.INSTANCE;
    }
}

✅ Advantage: Thread-safe, lazy, and no synchronization overhead.
❌ Downside: Still vulnerable to reflection and serialization attacks.


🛡️ 5. Enum Singleton (Most Robust)

public enum EnumSingleton {
    INSTANCE;
    public void doSomething() {
        // ...
    }
}

✅ Advantage: Thread-safe, lazy, and fully protected against reflection and serialization attacks.
🏆 This is the safest and most recommended approach, especially in libraries or critical systems.
📘 Recommended in Effective Java by Joshua Bloch.

Summary: Best Thread-Safe Singleton Approaches

ApproachLazy?RobustnessPerformance
🏆 Enum Singleton✅ Fully protected✅ Excellent
Static Inner Class❌ Vulnerable to reflection✅ Excellent
Double-Checked Locking❌ Vulnerable to reflection✅ Good
Eager Initialization❌ Vulnerable to reflection✅ Excellent
Synchronized Method❌ Vulnerable to reflection❌ Slower

⚠️ Disadvantages of the Singleton Pattern

While the Singleton pattern can be useful, it comes with several important disadvantages, especially when overused or misunderstood:

  • Global state: Acts like a global variable, making code harder to manage and test.

  • Hard to test: Difficult to mock or replace in unit tests.

  • Breaks SOLID principles: Often violates Single Responsibility and Dependency Inversion.

  • Concurrency issues: Poor implementation can create multiple instances in multi-threaded apps.

  • Long-lived lifecycle: Can lead to memory leaks or outdated state.

  • Tight coupling: Makes refactoring or replacing the singleton harder over time.

Conclusion

The Singleton pattern is a simple and effective tool for ensuring that a class has only one instance and a global point of access. It’s useful for managing shared resources like configuration, logging, or system-wide coordination.

Understanding the trade-offs between different implementations will help you choose the safest and most efficient approach for your needs — and avoid common pitfalls along the way.

We recommend the enum-based singleton for its simplicity and robustness. It’s inherently thread-safe, lazy, and protected against reflection and serialization attacks, making it the safest choice in most scenarios. It’s also the approach recommended by Effective Java author Joshua Bloch.

0
Subscribe to my newsletter

Read articles from Mirna De Jesus Cambero directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Mirna De Jesus Cambero
Mirna De Jesus Cambero

I’m a backend software engineer with over a decade of experience primarily in Java. I started this blog to share what I’ve learned in a simplified, approachable way — and to add value for fellow developers. Though I’m an introvert, I’ve chosen to put myself out there to encourage more women to explore and thrive in tech. I believe that by sharing what we know, we learn twice as much — that’s precisely why I’m here.