Singleton Pattern in Java: Ensuring a Single Instance

Singleton Pattern in Java: Ensuring a Single Instance

The Problem

Sometimes you need exactly one instance of a class across your whole application — a configuration manager, a connection pool, a logger. The Singleton pattern guarantees a class has only one instance and provides a global point of access to it.

Diagram

classDiagram
    class Singleton {
        -static Singleton instance
        -Singleton()
        +static getInstance() Singleton
        +doSomething()
    }
    note for Singleton "Private constructor +\nstatic accessor"

Java Example (thread-safe, lazy)

public final class ConfigurationManager {

    private static volatile ConfigurationManager instance;
    private final Map<String, String> settings = new ConcurrentHashMap<>();

    private ConfigurationManager() {
        settings.put("env", "production");
    }

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

    public String get(String key) {
        return settings.get(key);
    }
}

The classic double-checked locking is safe in modern Java because of the volatile keyword, but in real projects you rarely need to write this by hand — see the Quarkus example below.

Quarkus Example

In Quarkus (and CDI in general), you almost never hand-roll a Singleton. The container does it for you with @ApplicationScoped:

@ApplicationScoped
public class ConfigurationManager {

    private final Map<String, String> settings = new ConcurrentHashMap<>();

    @PostConstruct
    void init() {
        settings.put("env", "production");
    }

    public String get(String key) {
        return settings.get(key);
    }
}

CDI creates exactly one instance per application and injects it wherever needed:

@Path("/config")
public class ConfigResource {

    @Inject
    ConfigurationManager configurationManager;

    @GET
    public String env() {
        return configurationManager.get("env");
    }
}

Key Takeaways

  • Use Singleton when global, shared state truly needs a single owner.
  • In plain Java, guard against multithreading issues (volatile + double-checked locking, or an enum singleton).
  • In Quarkus/CDI, prefer @ApplicationScoped beans — the container manages the lifecycle for you, and it’s easier to test (you can mock the bean).
  • Overuse of Singleton can hide dependencies and hurt testability — inject it, don’t call a static getInstance() everywhere.