Design Patterns · Java

Singleton Design Pattern in Java

Understand when one shared instance is useful, how to implement it safely, and why Enum Singleton is often the strongest default.

What is the Singleton pattern?

Singleton is a creational design pattern that controls object creation so a class exposes one shared instance and a well-defined access point. The important part is not the static method itself; it is the lifecycle guarantee and the reason the shared resource should exist only once.

When is a single instance useful?

Use this pattern only when multiple instances would be incorrect or wasteful. Typical examples include a configuration snapshot, a process-wide cache, a metrics registry, or a carefully managed resource coordinator.

Use Singleton when:

  • Only one configuration manager is required.
  • A single cache manager should be shared.
  • A logging utility should be reused.
  • A shared resource must be controlled.
  • One application-wide object is enough.
ConfigurationOne consistent source of application settings.
LoggingA shared logging pipeline with one policy.
Resource managerCoordinate a pool or registry without duplicates.

Design caution: a Singleton is shared mutable state. Prefer dependency injection and immutable data when a normal service object is enough.

Common implementations

Each implementation makes a different trade-off between startup cost, lazy creation, simplicity, and concurrency safety.

1. Lazy initialization

The object is created on first access. This compact version is useful for learning, but it is not safe when several threads can call it at the same time.

public final class LazyConfig {
    private static LazyConfig instance;

    private LazyConfig() { }

    public static LazyConfig getInstance() {
        if (instance == null) {
            instance = new LazyConfig();
        }
        return instance;
    }
}
Good forSimple single-threaded examples.
Avoid forShared state accessed by concurrent requests.

2. Eager initialization

The JVM creates the instance while the class is initialized. Class initialization is thread-safe, so this is simple and reliable when the object is inexpensive.

public final class EagerConfig {
    private static final EagerConfig INSTANCE = new EagerConfig();

    private EagerConfig() { }

    public static EagerConfig getInstance() {
        return INSTANCE;
    }
}

3. Bill Pugh holder idiom

A nested holder delays creation until the accessor is called while relying on the JVM’s class-initialization guarantees. It is a strong non-enum option.

public final class FeatureFlags {
    private FeatureFlags() { }

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

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

Thread safety and double-checked locking

A check-then-create implementation can allow two request threads to observe a missing instance simultaneously. If lazy creation is required and the holder idiom cannot be used, double-checked locking can reduce synchronization overhead.

public final class ConnectionRegistry {
    private static volatile ConnectionRegistry instance;

    private ConnectionRegistry() { }

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

Why volatile? It prevents a thread from observing a reference before the object’s construction is safely visible. Without it, instruction reordering can make this pattern incorrect.

Enum Singleton: the practical default

For a true singleton that does not need lazy configuration, an enum is concise and benefits from Java’s guarantees around initialization and serialization.

public enum AppMetrics {
    INSTANCE;

    public void record(String event) {
        System.out.println("Recorded: " + event);
    }
}

AppMetrics.INSTANCE.record("user-login");
  • Safe initialization managed by the JVM.
  • Serialization preserves the enum constant.
  • Reflection cannot create another enum constant.
  • Easy to read and difficult to misuse.

Singleton scope in Spring Boot

Spring-managed beans use singleton scope by default: the container creates one bean instance per application context and injects that same instance wherever it is needed.

@Service
public class PaymentService {
    private final PaymentRepository repository;

    public PaymentService(PaymentRepository repository) {
        this.repository = repository;
    }
}

This is usually preferable to writing a static Singleton yourself because dependency injection keeps dependencies visible, improves testing, and lets Spring manage the lifecycle.

Trade-offs and interview summary

Advantages of Singleton

  • Ensures only one object exists.
  • Reduces unnecessary object creation.
  • Provides centralized access to shared data.
  • Is easy to implement using an enum.

Disadvantages of Singleton

  • Can behave like a global variable.
  • May make unit testing difficult.
  • Can create hidden dependencies.
  • May cause concurrency issues if not implemented correctly.

Real-World Examples

  • Configuration manager
  • Cache manager
  • Logging service
  • Application registry
  • Connection pool manager
ApproachLazyThread-safeRecommendation
Lazy fieldYesNoLearning only
Eager fieldNoYesSmall, cheap object
Holder idiomYesYesBest class-based option
EnumOn class useYesBest general default

Singleton can reduce duplicate resources, but it can also introduce hidden global state, tight coupling, and difficult tests. In modern applications, first consider a framework-managed singleton or a normal injected service.

Connection pool guidance: a database connection itself should usually not be implemented as a Singleton. Applications commonly use a connection pool that manages multiple reusable connections.

Key takeawayChoose the simplest lifecycle that satisfies the requirement. Use Enum Singleton for a true Java singleton, the holder idiom for a class-based lazy option, and Spring’s default scope for application services.