Singleton Pattern

One instance, global access point. The threading caveats, the why-everyone-says-don't, and when it's still right.

Pattern Foundational
8 min read
pattern creational singleton threading

What it is#

The Singleton pattern ensures a class has exactly one instance and provides a global point of access to it. The Gang of Four put it in the creational chapter because the pattern is really about controlling instantiation: the constructor is hidden, and a static accessor hands out the one and only instance.

The shape is small. A private constructor blocks new from outside the class. A private static field holds the singleton. A public static method — almost always called getInstance() — returns it. Everything else the class does is incidental; the pattern is the gate on construction.

The reason Singleton has a worse reputation than the other GoF patterns is that the “global point of access” half drags in every problem global state has ever caused: hidden dependencies, untestable code, ordering bugs at startup, and lifecycle confusion in containers. The pattern is still right in narrow cases — see “When to use it” — but reach for it last, not first.

Class structure#

┌────────────────────────────┐
│ Singleton │
├────────────────────────────┤
│ - instance : Singleton │ «static»
│ - Singleton() │ «private ctor»
├────────────────────────────┤
│ + getInstance() : Singleton│ «static»
│ + businessMethod() │
└────────────────────────────┘
│ returns
┌──────┴──────┐
│ Client │
└─────────────┘

The constructor is private; clients can only reach the instance through the static accessor.

When to use it#

Reach for Singleton when all of these hold:

  • The class truly represents a unique resource — a hardware connection, a process-wide cache, a configuration registry — where a second instance would be incorrect, not merely wasteful.
  • The lifecycle of that resource matches the lifecycle of the process.
  • The single instance has no per-request or per-user state that would leak between callers.
  • You cannot reasonably pass the dependency in through a constructor (DI container, framework boundary, legacy seam).

Common legitimate uses:

  • Loggers — the JDK’s java.util.logging.LogManager is effectively a singleton; one process, one log registry.
  • Configuration — a parsed-once, read-many Config object.
  • Caches and connection pools — but these are usually better owned by a DI container that also implements singleton scope.
  • Hardware abstractions — a printer spooler, a GPIO controller.

When not to use it:

  • When the “uniqueness” is a coincidence of the current deployment, not a property of the domain. Tomorrow’s deployment has two of them.
  • When the class has mutable state that any caller can write. Two threads, one global, no locking — a classic data race.
  • When you need it for testing isolation. Singletons leak state between tests; either reset them in @AfterEach (clunky) or refactor to dependency injection (cleaner).
  • When a DI container is available. Mark the class @Singleton (or equivalent) and let the container manage scope — you get singleton semantics without the global accessor.

How it works#

The four common Java implementations, with their threading properties. In modern Java, the enum form is the recommended idiom — it is concise, thread-safe by JVM guarantee, and serialization-safe for free.

// 1) Eager initialization — simple, thread-safe, but always pays the cost.
public final class EagerSingleton {
private static final EagerSingleton INSTANCE = new EagerSingleton();
private EagerSingleton() {}
public static EagerSingleton getInstance() { return INSTANCE; }
}
// 2) Double-checked locking — lazy and thread-safe. The `volatile` is non-negotiable.
public final class DclSingleton {
private static volatile DclSingleton instance;
private DclSingleton() {}
public static DclSingleton getInstance() {
DclSingleton local = instance; // read once into a local
if (local == null) {
synchronized (DclSingleton.class) {
local = instance;
if (local == null) {
local = new DclSingleton();
instance = local; // happens-before via volatile
}
}
}
return local;
}
}
// 3) Initialization-on-demand holder idiom — lazy via classloader, no synchronization.
public final class HolderSingleton {
private HolderSingleton() {}
private static final class Holder {
private static final HolderSingleton INSTANCE = new HolderSingleton();
}
public static HolderSingleton getInstance() { return Holder.INSTANCE; }
}
// 4) Enum singleton — the recommended idiom. Effective Java, Item 3.
public enum EnumSingleton {
INSTANCE;
private final Map<String, String> config = new ConcurrentHashMap<>();
public String get(String key) { return config.get(key); }
public void set(String key, String v) { config.put(key, v); }
}
// Usage:
// EnumSingleton.INSTANCE.set("region", "us-east-1");
// String r = EnumSingleton.INSTANCE.get("region");

Five non-obvious details:

  • volatile is required for DCL. Without it, a thread can see a partially constructed object — the reference is published before the constructor’s writes are visible. This was the bug that famously broke DCL on JVMs prior to JSR-133 (Java 5).
  • The holder idiom is lazy without locking. Classloading is synchronized by the JVM; Holder.INSTANCE initialises on first reference and only once. Free thread-safety, no volatile.
  • Enum singletons are serialization-safe. Standard Serializable singletons need a readResolve() method to defeat deserialization-creates-a-second-instance attacks. Enums get this for free.
  • Reflection can break ordinary singletons. Constructor.setAccessible(true) then newInstance() creates a second one. Enums refuse this — the JVM throws IllegalArgumentException.
  • Use final and a private constructor. final blocks subclassing (a subclass with a public constructor breaks the invariant); the private constructor blocks external new.

Variants#

VariantMechanismWhen it fits
EagerStatic field initialised at class load.Construction is cheap and the singleton is always used.
Lazy with DCLvolatile + double-checked locking.Construction is expensive and may not be needed; legacy code that cannot use enum.
Holder idiomStatic nested class holds the instance.The cleanest lazy form before enums; still common in libraries that pre-date Java 5.
Enum singletonenum X { INSTANCE; }.The modern default — thread-safe, serialization-safe, reflection-safe.
Registry / DI-managedContainer scopes the instance to the process.Anywhere a DI framework already exists; sidesteps the global-accessor problem.
Per-thread singletonThreadLocal holds one per thread.Strictly: one per thread, not one per process. Logging contexts, request-scoped state.

The enum form is the recommended idiom in modern Java. Reach for the holder idiom only when the singleton must implement an interface (enums can implement interfaces too, but the syntax surprises some readers).

Example systems#

The pattern shows up across the JDK and frameworks:

  • java.lang.Runtime.getRuntime() — one Runtime per JVM, exposed as a singleton.
  • java.util.Calendar.getInstance() — returns a calendar configured for the default locale and time zone (technically a factory method returning a new instance, but the access shape is the same).
  • java.util.logging.LogManager.getLogManager() — process-wide log registry.
  • java.awt.Desktop.getDesktop() — single desktop integration object.
  • Spring beans with default scope@Component-annotated classes are singletons inside the application context. The container, not the class, enforces uniqueness.

Trade-offs#

What you gain:

  • One instance, guaranteed. Any caller, anywhere, sees the same object.
  • Lazy initialisation when you want it. Holder idiom and DCL defer cost to first use.
  • Cheap access. A static field read is free.

What you pay:

  • Hidden dependencies. EnumSingleton.INSTANCE.set(...) does not appear in any method signature. Callers depend on the singleton invisibly; tests have to know to reset it.
  • Untestable code. You cannot pass in a fake. You can only mutate the real one or use bytecode rewriting frameworks (PowerMock, Mockito’s mockStatic). Both are smells.
  • Lifecycle confusion in containers. Web apps with multiple classloaders end up with one singleton per classloader, not one per process. WAR redeploys then leak the old instance.
  • Concurrency burden. Once a singleton has mutable state, every method becomes a candidate for a data race. Most singletons need synchronisation or immutable internal state.
  • The “anti-pattern” debate. The Singleton itself isn’t toxic — global mutable state is. A Logger singleton is fine; a CurrentUser singleton is poison. The nuance: if the singleton’s behaviour is stateless or its state is process-wide configuration set once at startup, the pattern is reasonable. If callers mutate it as part of business logic, prefer DI.

Singleton (getInstance())

  • Constructor is private; nobody else can make one.
  • Callers reach the instance through a static accessor.
  • Lifecycle tied to the JVM / classloader.
  • Hard to substitute in tests.

DI-managed singleton

  • Constructor is public; the container makes the one instance.
  • Callers receive the instance as a constructor argument.
  • Lifecycle tied to the container’s scope.
  • Trivial to substitute — pass a fake in tests.
  • Factory Method Pattern — Singleton restricts to one instance; Factory Method controls which concrete instance is created. They compose: a Singleton factory.
  • Builder Pattern — Builder constructs one complex object per call; Singleton constructs one and reuses it forever.
  • Prototype Pattern — the inverse philosophy: every caller gets a fresh copy, not the same one.
  • Dependency Inversion Principle (DIP) — the principle Singleton most often violates. Depending on Logger.getInstance() couples the caller to a concrete class; depending on a Logger interface passed in does not.
  • Single Responsibility Principle (SRP) — Singletons accumulate responsibilities over time (config + cache + clock + metrics) because they are easy to reach. Watch for this drift.
Search ESC

Keyboard shortcuts

Shortcuts are disabled while typing in inputs.