Singleton Pattern in Java: Beyond the Basics

“There should only ever be one.” — That sounds like the plot of a dystopian movie. But in software engineering, that’s the Singleton Pattern, and it plays a critical role in system design. And no, it’s not just a glorified global variable.

In this blog, we’ll go from a naive implementation to the most production-grade patterns of Singleton in Java. We’ll uncover the why, the how, and the why not of each variant, comparing them on safety, performance, and elegance.

📖 Not a Medium member? You can read this article for free using this friend link: Singleton Pattern in Java: Beyond the Basics

🔥 What is the Singleton Pattern?

The Singleton Pattern ensures that a class has only one instance in the JVM and provides a global access point to it.

Common Use Cases:

  • Configuration classes (e.g., AppConfig)
  • Logging services
  • Caching mechanisms
  • Thread pools
  • Database connection pools

Singleton is useful when:

  • You want a shared resource across the app
  • You want to control access to a single point of truth

🧱 The Naive Singleton (Thread-Unsafe)

public class AppConfig {

private static AppConfig instance;

private AppConfig() {}

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

❌ Problem:

  • This implementation is not thread-safe. If multiple threads call getInstance() at the same time, it can create multiple instances.
  • This breaks the singleton guarantee.

🔐 Synchronized Method (Thread-Safe but Slower)

public class AppConfig {

private static AppConfig instance;

private AppConfig() {}

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

✅ Thread-safe

❌ Performance hit:

  • Every call to getInstance() acquires a lock, even after the instance is initialized.

Use only in low-concurrency scenarios or educational demos.

⚡ Double-Checked Locking (with volatile)

public class AppConfig {

private static volatile AppConfig instance;

private AppConfig() {}

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

✅ Thread-safe

✅ Lazy-loaded

✅ High-performance

Why volatile?

Without volatile, the JVM might reorder instructions due to optimizations. This could result in a thread seeing a partially constructed object.

By marking the instance as volatile, you prevent that reordering and guarantee full object construction visibility across threads.

💡 Static Holder Class (Bill Pugh Singleton)

public class AppConfig {

private AppConfig() {}

private static class Holder {
private static final AppConfig INSTANCE = new AppConfig();
}

public static AppConfig getInstance() {
return Holder.INSTANCE;
}
}

✅ Thread-safe

✅ Lazy-loaded

✅ No synchronized, no volatile

JVM Classloading Guarantees:

  • Holder class is not loaded until getInstance() is called.
  • JVM ensures thread-safe loading of classes.

This is arguably the cleanest and most efficient Singleton pattern in Java.

🚀 Eager Initialization (Anti-pattern in Some Cases)

public class AppConfig {

private static final AppConfig instance = new AppConfig();

private AppConfig() {}

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

✅ Thread-safe

❌ Not lazy-loaded

  • Memory is consumed even if the instance is never used.
  • Useful only if you are 100% sure the singleton will be used.

🧠 When to Use Which Singleton?

Let’s simplify the decision-making:

Naive (non-thread-safe)

  • Not safe in multi-threaded environments.
  • Avoid in real applications.

Synchronized Method

  • Thread-safe, simple to write.
  • Performance suffers due to locking.
  • Use in low-concurrency scenarios or quick prototypes.

Double-Checked Locking (with volatile)

  • Lazy, fast, thread-safe.
  • More complex but suitable for performance-critical apps.

Static Holder Class

  • Best blend of simplicity and performance.
  • Leverages JVM guarantees.
  • Recommended for most real-world use cases.

Eager Initialization

  • Use if instance is guaranteed to be needed.
  • No overhead of lazy-loading.

🤔 Reflect & Apply — Questions for You:

If you can answer these, you’ve truly understood the Singleton pattern:

  1. What happens if volatile is removed from a double-checked locking Singleton?
  2. Why do we check if (instance == null) twice in that pattern?
  3. How does the JVM guarantee thread-safety with the static holder pattern?
  4. What are the dangers of using Singleton recklessly in large systems?
  5. Which Singleton variant would you use for a configuration loader that may or may not be used at runtime?

Take a few minutes. Think through them. If you’re unsure — scroll back up and re-read the explanations.

🫰 Final Thoughts

The Singleton pattern is deceptively simple. Many engineers fall into the trap of using it naively, turning their systems into a multithreaded mess.

In modern Java, your best bets are:

  • Static Holder for clean, lazy, thread-safe instantiation
  • Double-Checked Locking when you need explicit low-level control

Avoid eager singletons unless you must load it upfront. And never use non-thread-safe variants in real-world code.

📌 Bonus: Singleton is Not Always a Good Idea

Singletons are global state.

Overusing them:

  • Destroys testability
  • Introduces hidden dependencies
  • Encourages tight coupling

Use them only when a single shared instance is truly necessary.

📉 Enjoyed This?

If this helped you level up your design pattern game:

🖐 Clap a few times to show support
💬 Drop a comment: Was this clear? Did you learn something new?
👥 Follow me for upcoming deep-dives on:

  • Strategy Pattern — Dependency Injection’s Secret Weapon
  • Observer Pattern — Event-Driven Systems Done Right
  • Factory & Abstract Factory — Code That Builds Code

Let’s master software design — one pattern at a time.

Happy coding! ✨


Singleton Pattern in Java: Beyond the Basics was originally published in Javarevisited on Medium, where people are continuing the conversation by highlighting and responding to this story.

This post first appeared on Read More