Report this

What is the reason for this report?

Java Singleton Pattern: Best Practices & Examples

Updated on April 28, 2026
Java Singleton Pattern: Best Practices & Examples

Introduction

The Java Singleton Pattern is a creational design pattern that restricts a class to a single instance per Java Virtual Machine and exposes that instance through a global access point. It originates from the Gang of Four book Design Patterns (1994) and remains one of the most widely used patterns for managing shared resources such as logging, configuration, connection pools, and caches.

This guide covers seven implementation variants in Java: eager initialization, static block initialization, lazy initialization, synchronized method, double-checked locking, Bill Pugh, and enum singleton. It also covers the two attack vectors that can break the singleton guarantee, reflection and serialization, with prevention strategies for each. The final sections compare Singleton with static utility classes, explain how dependency injection frameworks such as Spring replace manual Singleton implementation, and cover Singleton behavior under virtual threads and GraalVM native image.

Key Takeaways

  • The Singleton pattern ensures a class has exactly one instance per JVM and provides a global access point to it.
  • Java offers seven common Singleton implementations: eager, static block, lazy, synchronized method, double-checked locking, Bill Pugh, and enum.
  • Bill Pugh (initialization-on-demand holder) is the recommended implementation when lazy loading is required; enum singleton is recommended in all other cases.
  • Double-checked locking requires the volatile keyword to prevent the JVM from publishing a partially constructed instance.
  • Reflection and serialization can both break Singleton guarantees on all implementations except enum singleton, which is immune to both attacks at the JVM level.
  • Avoid Singleton when it introduces hidden global state, blocks unit testing, or is used as a substitute for proper dependency management.
  • Spring beans are singleton-scoped by default within an ApplicationContext, which removes the need to implement the Singleton pattern manually in most modern Java applications.

What Is the Singleton Design Pattern in Java?

The Singleton pattern is a creational design pattern that guarantees a class has exactly one instance per JVM and exposes a global access point to retrieve it. It is used when one shared object must coordinate behavior across an application, such as a logger, configuration manager, or connection pool. The pattern is also used internally by the JDK in classes such as java.lang.Runtime and java.awt.Desktop.

Core Properties of a Singleton Class

Every Singleton implementation in Java shares three structural elements:

  1. Private constructor. Prevents external code from creating new instances with new.
  2. Private static instance field. Holds the single instance of the class.
  3. Public static accessor method. Returns the single instance, typically named getInstance().

The Singleton pattern is also used as a building block in other Java design patterns, including Abstract Factory, Builder, Prototype, and Facade.

When to Use the Singleton Pattern

  • Logging services where every component writes to the same destination.
  • Application configuration that must remain consistent across all callers.
  • Database connection pools and thread pools where the cost of creation is high and only one coordinator should exist.
  • In-memory caches that must serve a single source of truth.
  • Hardware or resource access objects such as device drivers and file system handles.

When to Avoid the Singleton Pattern

Avoid Singleton when it introduces problems that outweigh its benefits:

  • Global mutable state. Any thread can read and modify the instance at any time without the caller being aware of concurrent writers. A configuration Singleton that caches database credentials, for example, can serve stale values to one thread while another thread is mid-update, with no lock protecting the read.
  • Hidden dependencies. A class that calls Singleton.getInstance() internally declares no dependency in its constructor. The collaborator is invisible to the caller, which makes the class harder to reason about and impossible to rewire without editing source code.
  • Difficult unit testing. Because getInstance() always returns the live instance, you cannot substitute a mock or a test double without either modifying the Singleton class itself or using a bytecode manipulation library. Classes that depend on a Singleton directly are effectively untestable in isolation.
  • Single Responsibility Principle violations. A Singleton that starts as a logger often accumulates configuration reading, metrics emission, and feature flag evaluation over time because it is globally accessible and convenient to reach. The pattern provides no structural resistance to this kind of growth.
  • Classloader-heavy environments. In OSGi containers and Jakarta EE application servers, each deployed module or application runs in its own classloader. A Singleton initialized in one classloader is a completely separate object from the same class initialized in another. Applications that assume JVM-wide uniqueness will behave incorrectly in these environments.

In modern Java applications, dependency injection with Spring or Guice is the preferred alternative for most use cases that historically relied on a manual Singleton.

Eager Initialization Singleton

Use eager initialization when your Singleton is lightweight, has no constructor dependencies on runtime state, and will always be needed during the lifetime of the application. It is the correct default for simple shared resources such as a stateless utility wrapper or an application-wide constant registry. Avoid it when the object is heavyweight or when its constructor reads environment variables, opens file handles, or establishes network connections.

In eager initialization, the singleton instance is created when the class is loaded by the JVM. The instance exists before any code calls getInstance(), regardless of whether the application ever uses it. The JVM guarantees that class loading runs exactly once per classloader and is thread-safe, so no synchronization keyword is needed in the implementation.

Eager Initialization Implementation

package com.journaldev.singleton;

public class EagerInitializedSingleton {

    // Instance is created at class load time and assigned exactly once
    private static final EagerInitializedSingleton instance = new EagerInitializedSingleton();

    // Private constructor blocks external instantiation via `new`
    private EagerInitializedSingleton(){}

    // Global access point; returns the pre-created instance
    public static EagerInitializedSingleton getInstance() {
        return instance;
    }
}

Advantages and Limitations

Eager initialization is the simplest Singleton implementation in Java. The JVM guarantees that class initialization runs exactly once and is thread-safe, so no synchronization is needed.

Advantages:

  • Thread-safe by default through the JVM class loading guarantee.
  • Simplest implementation of all seven variants.
  • No synchronization overhead at access time.

Limitations:

  • The instance is created even if no code ever calls getInstance(), which wastes memory for heavyweight objects.
  • The constructor cannot throw a checked exception, so error handling at instantiation is limited.
  • Not appropriate when the Singleton holds expensive resources such as database connections or file handles that should be lazily created.

Static Block Initialization Singleton

Use static block initialization when your Singleton constructor can fail. Eager initialization does not allow the constructor to throw a checked exception because there is no try/catch wrapping the field initializer. If your Singleton reads a configuration file, opens a socket, or loads a native library during construction, a static block gives you a place to catch the failure and convert it to a RuntimeException that halts class loading with a clear error rather than leaving the application in a partially initialized state.

Static block initialization is structurally identical to eager initialization in every other respect: the instance is created at class load time, it is thread-safe by the JVM class loading guarantee, and it is not lazy.

Static Block Implementation

package com.journaldev.singleton;

public class StaticBlockSingleton {

    private static StaticBlockSingleton instance;

    private StaticBlockSingleton(){}

    // Static block runs once at class load; wraps construction in try/catch
    static {
        try {
            instance = new StaticBlockSingleton();
        } catch (Exception e) {
            // Convert any checked exception into RuntimeException so class loading fails fast
            throw new RuntimeException("Exception occurred in creating singleton instance");
        }
    }

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

When to Use Static Block Over Eager Initialization

Use static block initialization when the Singleton constructor may fail with a checked exception (for example, while reading configuration from disk or initializing a network resource). Both approaches load the instance eagerly, so neither is appropriate for heavyweight resources that should be created lazily.

Lazy Initialization Singleton

Lazy initialization defers Singleton creation until the first call to getInstance(). The instance is not created at class load time, which is appropriate for heavyweight Singletons that may not be needed in every program run.

Lazy Initialization Implementation

package com.journaldev.singleton;

public class LazyInitializedSingleton {

    private static LazyInitializedSingleton instance;

    private LazyInitializedSingleton(){}

    public static LazyInitializedSingleton getInstance() {
        // Race condition exposure: multiple threads can pass this check simultaneously
        if (instance == null) {
            instance = new LazyInitializedSingleton();
        }
        return instance;
    }
}

Thread Safety Problem with Naive Lazy Initialization

Warning: This implementation is not thread-safe and breaks the Singleton guarantee under concurrent access. Use it only in confirmed single-threaded contexts.

Two threads can interleave inside the null check and produce two distinct instances:

  1. Thread A evaluates instance == null. Result: true.
  2. Thread B evaluates instance == null before Thread A finishes constructing the object. Result: true.
  3. Both threads execute new LazyInitializedSingleton().
  4. Two instances are created and the Singleton guarantee is broken.

The next sections cover four thread-safe alternatives, ordered from highest synchronization cost to lowest.

Thread-Safe Singleton with Synchronized Method

If you need lazy initialization and thread safety and you are not concerned about performance under high concurrency, marking getInstance() as synchronized is the simplest correct solution. Only one thread can enter the method at a time, which closes the race condition described in the previous section. The trade-off is that every call to getInstance() acquires a monitor lock, even the millions of calls that happen after the instance has already been created. For applications where getInstance() is called infrequently, this overhead is irrelevant. For hot paths called by thousands of threads per second, use Bill Pugh instead.

Implementation

package com.journaldev.singleton;

public class ThreadSafeSingleton {

    private static ThreadSafeSingleton instance;

    private ThreadSafeSingleton(){}

    // synchronized serializes all callers; only one thread enters the method at a time
    public static synchronized ThreadSafeSingleton getInstance() {
        if (instance == null) {
            instance = new ThreadSafeSingleton();
        }
        return instance;
    }

}

Performance Cost of Synchronization

Every call to synchronized getInstance() acquires the class monitor, which involves a compare-and-swap on the lock word and a memory barrier. This cost is paid on every call, including the millions of calls that happen after the instance has already been created and the lock no longer serves any purpose. Under contention, all callers serialize on the monitor and throughput collapses. Double-checked locking solves this by synchronizing only during the brief window when the instance is being created, after which subsequent calls perform a single volatile read.

Double-Checked Locking Singleton in Java

Double-checked locking checks instance == null twice: once outside the synchronized block to avoid the lock when the instance already exists, and once inside the synchronized block to prevent two threads from both creating the instance after passing the first check.

Why volatile Is Required

Object construction in the JVM happens in three steps: (1) allocate memory, (2) run the constructor to initialize fields, (3) assign the reference to instance. Without volatile, the JVM or the CPU is allowed to reorder steps 2 and 3. A second thread can then read a non-null instance reference that points to a partially constructed object.

Declaring the instance variable volatile prevents this reordering and establishes a happens-before relationship between the write and any subsequent read across threads.

Implementation with Annotated Code

package com.journaldev.singleton;

public class DoubleCheckedLockingSingleton {

    // volatile ensures visibility across threads and prevents instruction reordering
    private static volatile DoubleCheckedLockingSingleton instance;

    private DoubleCheckedLockingSingleton() {}

    public static DoubleCheckedLockingSingleton getInstance() {
        // First check: avoid acquiring the lock if the instance is already created
        if (instance == null) {
            // Synchronize only during the brief initialization window
            synchronized (DoubleCheckedLockingSingleton.class) {
                // Second check: prevent duplicate creation if two threads passed the first check
                if (instance == null) {
                    instance = new DoubleCheckedLockingSingleton();
                }
            }
        }
        return instance;
    }
}

JVM Memory Model Guarantees

Note: This guarantee depends on the Java 5+ memory model defined by JSR-133. On JDKs prior to Java 5, double-checked locking was broken even with volatile.

Under JSR-133, a write to a volatile variable happens-before any subsequent read of that variable across threads. The JVM is required to publish the fully constructed object before any thread observes the non-null reference, so no thread can ever see a partially initialized instance through the volatile field.

Continue your learning with Java Thread Safety.

Bill Pugh Singleton Implementation

The Bill Pugh Singleton, also known as the initialization-on-demand holder idiom, uses an inner static helper class to defer instance creation until first access without any explicit synchronization. The pattern was proposed by Bill Pugh as a response to the unreliability of double-checked locking under the pre-Java 5 memory model.

How the Inner Static Helper Class Works

package com.journaldev.singleton;

public class BillPughSingleton {

    private BillPughSingleton(){}

    // Inner static class is not loaded until referenced for the first time
    private static class SingletonHelper {
        // INSTANCE is created when SingletonHelper is initialized by the JVM
        private static final BillPughSingleton INSTANCE = new BillPughSingleton();
    }

    public static BillPughSingleton getInstance() {
        // First reference to SingletonHelper triggers its class initialization
        return SingletonHelper.INSTANCE;
    }
}

SingletonHelper is not loaded when BillPughSingleton is loaded. The JVM loads it only when getInstance() is called for the first time, at which point class initialization runs and INSTANCE is created. Class initialization is inherently thread-safe because the JVM acquires an internal lock for the duration of class initialization, so no synchronized block or volatile field is required.

Why This Is Preferred Over Double-Checked Locking

The Bill Pugh implementation removes the synchronization complexity and overhead of double-checked locking:

  • No explicit synchronized block at the call site.
  • No volatile field.
  • Lazy by construction: the instance is created on first access, not at class load.
  • Cleaner code with no double null check.

Bill Pugh is the recommended general-purpose Singleton implementation when lazy loading is required.

Enum Singleton in Java

The enum Singleton declares a single-value enum type whose sole constant serves as the singleton instance. The JVM guarantees that the constant is instantiated exactly once and provides built-in protection against both reflection and serialization attacks. Joshua Bloch recommends this approach in Effective Java (Item 3).

Implementation Example

package com.journaldev.singleton;

public enum EnumSingleton {

    // INSTANCE is the sole enum constant and the singleton instance
    INSTANCE;

    // Instance fields are safe to add; each enum constant gets its own copy
    private String databaseUrl = "jdbc:postgresql://localhost:5432/appdb";
    private int maxConnections = 10;

    public String getDatabaseUrl() {
        return databaseUrl;
    }

    public void setDatabaseUrl(String url) {
        // In production, guard writes with validation or make the field final
        this.databaseUrl = url;
    }

    public int getMaxConnections() {
        return maxConnections;
    }
}

External code accesses the singleton through the enum constant:

// Retrieve the singleton and read configuration
String url = EnumSingleton.INSTANCE.getDatabaseUrl();
int maxConn = EnumSingleton.INSTANCE.getMaxConnections();

// Update configuration at runtime
EnumSingleton.INSTANCE.setDatabaseUrl("jdbc:postgresql://prod-host:5432/appdb");

Why Joshua Bloch Recommends Enum Singleton

Three properties make enum singleton the safest implementation:

  1. Reflection-proof. The JVM forbids reflective instantiation of enum types. Constructor.newInstance() on an enum throws IllegalArgumentException.
  2. Serialization-proof. The JVM handles enum deserialization by returning the existing constant rather than calling the constructor or readObject(). No readResolve() is needed.
  3. Concise. The implementation is the shortest of all seven approaches.

Enum singleton is the recommended Singleton implementation in all cases where lazy initialization is not required.

Limitations of Enum Singleton

  • No lazy initialization. The instance is created when the enum class is loaded.
  • No superclass inheritance. Enums implicitly extend java.lang.Enum and cannot extend any other class. They can implement interfaces.

Singleton vs Static Class in Java

A Singleton and a class with only static members both provide a single point of access to shared state, but they differ in capability and intent.

Key Differences Table

Characteristic Singleton Static Class
Instantiation Single instance, created on demand or at load No instantiation; static members only
Interface implementation Yes No
Inheritance Can extend a superclass Cannot extend a class (implicitly final in effect)
Lazy initialization Supported (Bill Pugh, double-checked locking) Not applicable
Serialization Supported with readResolve() Not applicable
Testability Can be mocked when injected as a dependency Difficult to mock
State Encapsulated in instance Shared via static fields
Polymorphism Supported Not supported

When to Choose Each Approach

Use a Singleton when the object holds state, must implement an interface for abstraction, may need lazy loading, or must be replaced with a mock during unit testing.

Use a static class when the API is a stateless utility with no instance lifecycle and no need for polymorphism. Examples in the JDK are java.util.Collections and java.util.Arrays.

Breaking Singleton with Reflection and How to Prevent It

Reflection can break every Singleton implementation except enum by accessing the private constructor at runtime. Enum singleton is immune at the JVM level.

Reflection Attack Example

Warning: Reflection bypasses the private access modifier on the constructor and breaks the Singleton guarantee on eager, static block, lazy, synchronized, double-checked locking, and Bill Pugh implementations.

package com.journaldev.singleton;

import java.lang.reflect.Constructor;

public class ReflectionSingletonTest {

    public static void main(String[] args) {
        EagerInitializedSingleton instanceOne = EagerInitializedSingleton.getInstance();
        EagerInitializedSingleton instanceTwo = null;
        try {
            Constructor[] constructors = EagerInitializedSingleton.class.getDeclaredConstructors();
            for (Constructor constructor : constructors) {
                // setAccessible(true) bypasses the private modifier on the constructor
                constructor.setAccessible(true);
                // newInstance() creates a second instance, breaking the singleton guarantee
                instanceTwo = (EagerInitializedSingleton) constructor.newInstance();
                break;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(instanceOne.hashCode());
        System.out.println(instanceTwo.hashCode());
    }

}

The two printed hash codes will differ, which confirms that two distinct instances exist. The exact values vary per JVM run.

Prevention Strategy

Add a guard check in the private constructor that throws when an instance already exists:

private EagerInitializedSingleton() {
    // Block reflective instantiation after the singleton is created
    if (instance != null) {
        throw new IllegalStateException(
            "Singleton instance already exists. Use getInstance() instead."
        );
    }
}

This guard works for eager and static block initialization because instance is non-null by the time any reflection call runs. It does not work reliably for lazy implementations because instance is null during the first legitimate construction, which means a reflection call that races with getInstance() can still succeed.

For full protection against reflection attacks, use enum singleton. The JVM rejects Constructor.newInstance() calls on enum types with IllegalArgumentException, which is the only approach immune to reflection at the JVM level. Continue your learning with the Java Reflection Tutorial.

Breaking Singleton with Serialization and How to Prevent It

A Singleton that implements Serializable can be written to and read from a file or network stream. This support is required in many distributed systems, but it introduces a second attack vector that breaks the Singleton guarantee.

The Serialization Problem

Warning: Java’s default deserialization mechanism bypasses the constructor and creates a new object instance. Without an explicit fix, every readObject() call returns a fresh instance and breaks the Singleton guarantee.

package com.journaldev.singleton;

import java.io.Serializable;

public class SerializedSingleton implements Serializable {

    private static final long serialVersionUID = -7604766932017737115L;

    private SerializedSingleton(){}

    private static class SingletonHelper {
        private static final SerializedSingleton instance = new SerializedSingleton();
    }

    public static SerializedSingleton getInstance() {
        return SingletonHelper.instance;
    }

}

The following test serializes the singleton, deserializes it, and prints both hash codes:

package com.journaldev.singleton;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;

public class SingletonSerializedTest {

    public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
        SerializedSingleton instanceOne = SerializedSingleton.getInstance();
        ObjectOutput out = new ObjectOutputStream(new FileOutputStream(
                "filename.ser"));
        out.writeObject(instanceOne);
        out.close();

        // Default deserialization bypasses the constructor and creates a new instance
        ObjectInput in = new ObjectInputStream(new FileInputStream(
                "filename.ser"));
        SerializedSingleton instanceTwo = (SerializedSingleton) in.readObject();
        in.close();

        System.out.println("instanceOne hashCode="+instanceOne.hashCode());
        System.out.println("instanceTwo hashCode="+instanceTwo.hashCode());

    }

}

The two printed hash codes will differ, which confirms that deserialization produced a second instance. The exact values vary per JVM run.

Using readResolve() to Preserve Singleton Behavior

Add readResolve() as an instance method directly inside the outer Singleton class, not inside SingletonHelper. The JVM calls this method on the deserialized object immediately after construction and substitutes its return value, discarding the freshly created object:

package com.journaldev.singleton;

import java.io.Serializable;

public class SerializedSingleton implements Serializable {

    private static final long serialVersionUID = -7604766932017737115L;

    private SerializedSingleton(){}

    private static class SingletonHelper {
        private static final SerializedSingleton instance = new SerializedSingleton();
    }

    public static SerializedSingleton getInstance() {
        return SingletonHelper.instance;
    }

    // readResolve() is called by the JVM after deserialization; returning getInstance()
    // causes the JVM to discard the deserialized object and return the existing instance
    protected Object readResolve() {
        return getInstance();
    }
}

After adding readResolve(), the two printed hash codes will match, which confirms that deserialization returned the existing instance.

Enum singleton handles this case automatically without requiring readResolve(), because the JVM’s enum deserialization always returns the existing constant. For background on the underlying APIs, see Java Serialization and Java Deserialization.

Singleton in Spring and Dependency Injection Frameworks

Most modern Java applications use a dependency injection container instead of implementing the Singleton pattern manually. The container manages the single instance and injects it into every collaborator that needs it.

Spring Bean Scope vs Manual Singleton

In Spring, every bean is singleton-scoped by default within an ApplicationContext. Annotating a class with @Service, @Component, or @Repository is sufficient.

import org.springframework.stereotype.Service;

@Service
// Spring manages exactly one instance of this class per ApplicationContext
public class ConfigurationService {

    public String getDatabaseUrl() {
        return "jdbc:postgresql://localhost:5432/mydb";
    }
}

The scope is per ApplicationContext, not per JVM. A single JVM that bootstraps two contexts will create two instances of the bean. This differs from the Singleton pattern’s JVM-level guarantee, but it matches what real applications need: per-application isolation rather than process-wide global state.

Testing Singletons with Dependency Injection

Manual singletons cannot be replaced with mocks in unit tests without modifying the class under test. Constructor injection eliminates this problem.

// Inject the service rather than calling ConfigurationService.getInstance()
public class DatabaseConnector {

    private final ConfigurationService configService;

    // Constructor injection allows passing a mock in unit tests
    public DatabaseConnector(ConfigurationService configService) {
        this.configService = configService;
    }

    // Delegates to the injected service; no static getInstance() call in the method body
    public String getDatabaseUrl() {
        return configService.getDatabaseUrl();
    }
}

Constructor injection with Spring or Guice removes the need for a manual Singleton implementation in most production Java applications and produces classes that are testable in isolation.

The following JUnit 5 and Mockito example shows what a test for DatabaseConnector looks like when the dependency is injected rather than fetched via getInstance():

import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class DatabaseConnectorTest {

    @Test
    void testGetUrlUsesInjectedConfig() {
        // Create a mock that returns a controlled value instead of the real singleton
        ConfigurationService mockConfig = Mockito.mock(ConfigurationService.class);
        Mockito.when(mockConfig.getDatabaseUrl()).thenReturn("jdbc:h2:mem:testdb");

        // Inject the mock through the constructor; no singleton is involved
        DatabaseConnector connector = new DatabaseConnector(mockConfig);

        assertEquals("jdbc:h2:mem:testdb", connector.getDatabaseUrl());
    }
}

This test runs without a Spring context, without a real database, and without touching the live ConfigurationService singleton. If DatabaseConnector called ConfigurationService.getInstance() directly, this test would be impossible to write without modifying production code or using a bytecode manipulation library.

Singleton Behavior with Virtual Threads and GraalVM Native Image

Two recent additions to the Java platform change how Singleton implementations behave at runtime: virtual threads from Project Loom and GraalVM native image.

Virtual threads share heap state with platform threads, so all seven Singleton implementations remain correct under virtual threads. In JDK 21 and 22, a virtual thread that enters a synchronized block is pinned to its carrier thread for the duration of the block, which affects throughput under contention but not correctness. JDK 24 removed this pinning behavior for most cases. Bill Pugh and enum singleton avoid the concern entirely because neither acquires a synchronized lock on the access path.

GraalVM native image initializes classes at build time by default. This means an eager or static block Singleton is constructed during the native image build rather than at first runtime access. If the constructor reads an environment variable such as System.getenv("DB_URL") or opens a socket, that call runs on the build machine where the variable does not exist and the network is not the deployment network. The Singleton captures that state permanently into the binary.

The symptom is a null field or a connection pointing to a build-time address that does not exist at runtime. GraalVM may also fail the build with an error such as:

Error: Classes that should be initialized at run time got initialized during image building

To fix this, tell GraalVM to defer class initialization to runtime using the --initialize-at-run-time flag. Pass the fully qualified class name of the Singleton:

native-image --initialize-at-run-time=com.journaldev.singleton.EagerInitializedSingleton \
  -jar myapp.jar

Bill Pugh and enum singletons are unaffected when accessed only at runtime, because neither class is initialized until its first access, which happens on the deployment host after the binary has started.

Comparison of All Singleton Implementations

Summary Table: Thread Safety, Lazy Loading, Reflection Safety, Serialization Safety

Implementation Thread Safe Lazy Loading Reflection Safe Serialization Safe Recommended
Eager Initialization Yes (class loading) No No No (needs readResolve) Low-overhead resources only
Static Block Initialization Yes (class loading) No No No (needs readResolve) When constructor throws checked exception
Lazy Initialization No Yes No No Single-threaded only
Synchronized Method Yes Yes No No Not recommended (performance)
Double-Checked Locking Yes (with volatile) Yes No No (needs readResolve) Acceptable; prefer Bill Pugh
Bill Pugh (Holder) Yes Yes No No (needs readResolve) Recommended when lazy loading is required
Enum Singleton Yes No Yes Yes Recommended in all other cases

Frequently Asked Questions

Q: What is the Singleton pattern in Java and why is it used?

A: The Singleton pattern is a creational design pattern that restricts a class to exactly one instance and provides a global access point to retrieve it. It is used when a single shared resource must coordinate actions across a system, such as a logging service, application configuration manager, or database connection pool. The pattern was introduced in the Gang of Four book Design Patterns (1994) and is one of the most widely recognized patterns in Java.

Q: Which Singleton implementation is the best choice for a multithreaded Java application?

A: The Bill Pugh Singleton (initialization-on-demand holder idiom) is the recommended general-purpose implementation when lazy loading is required. It is thread-safe without explicit synchronization and requires no volatile keyword. The enum singleton is the recommended choice when lazy initialization is not required, because it is also immune to reflection and serialization attacks at the JVM level.

Q: What is double-checked locking in Java Singleton and why is volatile required?

A: Double-checked locking reduces synchronization overhead by checking whether the instance is null before and after acquiring the lock. Without the volatile keyword, the JVM or CPU may reorder the steps of object construction (memory allocation, initialization, reference assignment), allowing a second thread to read a non-null but incompletely initialized instance. Declaring the instance variable volatile enforces a happens-before relationship that prevents this reordering.

Q: How does the enum Singleton prevent reflection and serialization attacks?

A: The JVM prohibits reflective instantiation of enum types; calling Constructor.newInstance() on an enum throws IllegalArgumentException. For serialization, the JVM handles enum deserialization by returning the existing constant rather than creating a new instance, so readResolve() is not needed. These guarantees are provided at the JVM level and cannot be circumvented.

Q: What is the difference between a Java Singleton and a static class?

A: A Singleton is an object instance with encapsulated state, capable of implementing interfaces, supporting inheritance, and being passed as a dependency. A static class (a class with only static methods and fields) cannot be instantiated, cannot implement interfaces in a polymorphic context, and cannot be easily mocked in unit tests. Use Singleton when you need stateful behavior or interface-based abstraction; use a static class for stateless utility methods.

Q: Can a Singleton be broken by serialization in Java?

A: Yes. Java’s default deserialization mechanism bypasses the constructor and creates a new object, which results in a second instance and breaks the Singleton guarantee. To prevent this, implement the readResolve() method to return the existing instance. Alternatively, use enum singleton, which is immune to serialization attacks because the JVM handles enum deserialization by returning the existing constant.

Q: How do I use a Singleton pattern in a Spring application?

A: Spring beans are singleton-scoped by default within an ApplicationContext. Annotating a class with @Service, @Component, or @Repository causes Spring to create and manage exactly one instance per context. This is the preferred approach in Spring applications because the container handles lifecycle management and the bean can be injected as a constructor parameter, making it testable with mock objects.

Q: Is the Singleton pattern considered an anti-pattern?

A: The Singleton pattern is considered an anti-pattern in contexts where it introduces global mutable state, hides dependencies, or makes unit testing difficult. These problems arise most often when Singleton is called directly via getInstance() throughout the codebase rather than injected as a dependency. In modern Java applications using Spring or Guice, manual Singleton implementation is rarely necessary. The pattern is appropriate for stateless shared resources or infrastructure objects where a single coordinating instance is a genuine architectural requirement.

Conclusion

This article covered seven Singleton implementation variants in Java (eager, static block, lazy, synchronized method, double-checked locking, Bill Pugh, and enum), the two attack vectors that can break the Singleton guarantee (reflection and serialization) and how to prevent each, the structural comparison between Singleton and static utility classes, the role of dependency injection frameworks such as Spring as a modern alternative, and Singleton behavior under virtual threads and GraalVM native image.

You can now select the correct Singleton implementation for your use case based on whether lazy loading is required, apply volatile correctly when implementing double-checked locking, protect your Singletons against reflection and serialization attacks, and decide when to replace a manual Singleton with a Spring-managed bean.

For deeper coverage of related topics, see:

Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.

Learn more about our products

About the author(s)

Pankaj Kumar
Pankaj Kumar
Author
See author profile

Java and Python Developer for 20+ years, Open Source Enthusiast, Founder of https://www.askpython.com/, https://www.linuxfordevices.com/, and JournalDev.com (acquired by DigitalOcean). Passionate about writing technical articles and sharing knowledge with others. Love Java, Python, Unix and related technologies. Follow my X @PankajWebDev

Vinayak Baranwal
Vinayak Baranwal
Editor
Technical Writer II
See author profile

Building future-ready infrastructure with Linux, Cloud, and DevOps. Full Stack Developer & System Administrator. Technical Writer @ DigitalOcean | GitHub Contributor | Passionate about Docker, PostgreSQL, and Open Source | Exploring NLP & AI-TensorFlow | Nailed over 50+ deployments across production environments.

Still looking for an answer?

Was this helpful?

owesome post…evrything is discussed about singleton.keep it up…nd thanks

- poonam

Very nice post:-) keep up the good work

- erick

nice tutorial thanks … i have one question fro you How annotations works internally?

- Tarun Soni

Sir, As per u provide contains which one is the best practices for the Singleton Pattern. Regards Vishnu

- Vishnu

This web site useful for me, Thanks a ton.

- Vishnu

Hi Pankaj, Can we create more than one object of a singleton class using serialization ? Please provide your view in detail about in which condition Singleton classes can have more than one objects. Thanks in advance. Regards, H Singh

- H Singh

Very good site for design patterns. Explanation is easy and up to the mark. Thanks.

- Prakash

This is a nice article and almost everything is covered. Thanks a ton.

- Anshul Jain

Very nice tutorial. It help me lot to understand the singleton. Could you please help me how protected Object readResolve() method make i class a singleton.

- DHARMENDRA KUMAR SAHU

Very nice, explained in simple terms.

- Rajesh

Creative CommonsThis work is licensed under a Creative Commons Attribution-NonCommercial- ShareAlike 4.0 International License.
Join the Tech Talk
Success! Thank you! Please check your email for further details.

Please complete your information!

The developer cloud

Scale up as you grow — whether you're running one virtual machine or ten thousand.

Start building today

From GPU-powered inference and Kubernetes to managed databases and storage, get everything you need to build, scale, and deploy intelligent applications.

Dark mode is coming soon.