How To Make Class As Singleton In Java

5 min read

The Singleton pattern remains one of the most recognized and debated design patterns in Java development. This is critical for scenarios where a single object must coordinate actions across a system, such as logging frameworks, configuration managers, database connection pools, or thread pools. Worth adding: implementing this pattern correctly in Java requires a deep understanding of class loading mechanisms, concurrency, and serialization behaviors. At its core, a Singleton ensures that a class has only one instance while providing a global point of access to that instance. This guide explores the various approaches to creating a Singleton in Java, analyzing the trade-offs of each method to help you choose the right implementation for your specific architectural needs.

Understanding the Core Requirements

Before diving into code implementations, Define what makes a Singleton implementation strong — this one isn't optional. A proper Singleton class must satisfy three fundamental criteria:

  1. Single Instance: The class must strictly prevent the creation of more than one instance.
  2. Global Access: It must provide a static method (traditionally getInstance()) that allows clients to access the unique instance.
  3. Lazy Initialization (Optional but Preferred): The instance should ideally be created only when it is first requested, saving resources if the object is never used.

Additionally, a production-grade implementation must handle thread safety (preventing multiple threads from creating instances simultaneously), serialization safety (preventing new instances during deserialization), and reflection safety (preventing instantiation via Constructor.setAccessible(true)).

The Enum Approach: The Gold Standard

Since Java 5, the most concise, safe, and recommended way to implement a Singleton is using an enum. This approach is championed by Joshua Bloch in Effective Java because it handles serialization, reflection attacks, and thread safety automatically by the JVM.

public enum DatabaseConnection {
    INSTANCE;

    private Connection connection;

    DatabaseConnection() {
        // Initialization logic runs once during class loading
        this.connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb");
    }

    public Connection getConnection() {
        return connection;
    }
}

Usage: DatabaseConnection.INSTANCE.getConnection();

Why this wins:

  • Thread Safety: Guaranteed by the Java Language Specification (JLS) during enum constant initialization.
  • Serialization Safety: The JVM ensures enum constants are singletons during deserialization; readResolve is not needed.
  • Reflection Safety: Reflection cannot instantiate an enum; Constructor.newInstance() throws an IllegalArgumentException.
  • Simplicity: Zero boilerplate code.

Limitation: Enums do not support lazy initialization (the instance is created when the class is loaded) and cannot extend other classes (though they can implement interfaces) Simple, but easy to overlook. Took long enough..

Bill Pugh Singleton: Lazy Initialization Holder Class

If you require lazy initialization without the overhead of synchronization on every method call, the Bill Pugh Singleton (Initialization-on-Demand Holder Idiom) is the standard pre-Java 5 solution that remains highly relevant. It leverages the Java class loading mechanism: inner classes are not loaded until they are referenced.

public class AppConfig {
    // Private constructor prevents instantiation from other classes
    private AppConfig() {
        // Expensive initialization logic here
        loadConfiguration();
    }

    // Static inner class holds the instance
    private static class SingletonHolder {
        private static final AppConfig INSTANCE = new AppConfig();
    }

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

    private void loadConfiguration() {
        // Simulate heavy lifting
    }
}

How it works: When AppConfig is loaded, SingletonHolder is not loaded. Only when getInstance() is called does the JVM load SingletonHolder, initializing the static final INSTANCE. The JLS guarantees this initialization is atomic and thread-safe without explicit synchronized keywords Worth keeping that in mind..

Pros: Lazy initialization, high performance (no synchronization overhead after initialization), simple syntax. Cons: Vulnerable to reflection attacks (can be mitigated by throwing an exception in the constructor if INSTANCE already exists) and requires readResolve() for serialization safety Small thing, real impact..

Double-Checked Locking: The Volatile Keyword

Before the Bill Pugh idiom gained popularity, Double-Checked Locking (DCL) was the go-to for lazy initialization. Consider this: early versions were broken due to the Java Memory Model allowing instruction reordering (the instance reference could be assigned before the constructor finished). This was fixed in Java 5 with the enhanced semantics of the volatile keyword Took long enough..

public class Logger {
    // Volatile ensures visibility and prevents instruction reordering
    private static volatile Logger instance;

    private Logger() {}

    public static Logger getInstance() {
        // First check (no locking)
        if (instance == null) {
            synchronized (Logger.class) {
                // Second check (with locking)
                if (instance == null) {
                    instance = new Logger();
                }
            }
        }
        return instance;
    }
}

Critical Detail: The volatile keyword is non-negotiable here. Without it, a thread might see a non-null reference to a partially constructed object. The first null check avoids the performance penalty of synchronization after the instance is created Worth knowing..

Pros: Lazy initialization, standard pattern recognized by many developers. Cons: Verbose, slightly error-prone if volatile is forgotten, vulnerable to reflection/serialization without extra code The details matter here..

Eager Initialization: Simple but Rigid

If the cost of creating the instance is negligible or the class is guaranteed to be used in every application run, eager initialization is the simplest valid approach.

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

    private CacheManager() {}

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

Pros: Extremely simple, thread-safe by nature of class loading. Cons: No lazy loading (instance created even if never used), no exception handling in static initializer (though a static block can handle exceptions), vulnerable to reflection/serialization.

Hardening the Singleton: Serialization and Reflection

For non-enum implementations, you must explicitly defend against two mechanisms that can break the Singleton contract.

1. Serialization Safety

If your Singleton implements Serializable, deserialization creates a new instance by default. You must provide a readResolve method to return the existing instance.

// Add this to any non-enum Singleton class implementing Serializable
protected Object readResolve() {
    return getInstance(); // Return the singleton instance
}

2. Reflection Safety

Reflection allows calling the private constructor via constructor.setAccessible(true). To prevent this, modify the constructor to throw an exception if an instance already exists.

private Singleton() {
    if (INSTANCE != null) { // Or check a static boolean flag
        throw new IllegalStateException("Singleton instance already created.");
    }
    // Initialization logic
}

Note: This check works for Eager, DCL, and Bill Pugh. For Enum, this is handled natively by the JVM.

Comparison Summary: Choosing the Right Strategy

Approach Lazy Init Thread Safe Serialization Safe Reflection Safe Complexity
Enum No Yes Yes (Native) Yes (Native) Lowest
Bill Pugh Yes Yes No (Needs readResolve) No (Needs Constructor Check) Low
Double-Checked Locking Yes Yes (w/ volatile) No (Needs readResolve) No (
What's New

Out This Morning

Explore More

Before You Go

Thank you for reading about How To Make Class As Singleton In Java. We hope the information has been useful. Feel free to contact us if you have any questions. See you next time — don't forget to bookmark!
⌂ Back to Home