The Singleton pattern guarantees a class has only one instance and provides global access to it. The GoF book popularised it; a generation of programmers used it everywhere; the same generation has spent years getting away from it.
By 2026, the consensus is: prefer dependency injection. Singleton specifically as a pattern is rarely the right answer. This page is the reasoning and the alternatives.
Classic implementation:
public class Logger {
private static Logger instance;
private Logger() { /* private constructor */ }
public static Logger getInstance() {
if (instance == null) {
instance = new Logger();
}
return instance;
}
public void log(String msg) { /* ... */ }
}
// Usage:
Logger.getInstance().log("hello");
Two assertions: (1) only one instance ever exists; (2) you access it via static method.
It solves a real problem: some things genuinely should be one. Database connection pools, configuration managers, loggers, caches.
It also makes them globally accessible. Easy to use anywhere; no need to pass the dependency through layers.
These two properties — single instance + global access — are what made it popular and what made it problematic.
A class that uses Logger.getInstance() has Logger as a dependency, but its constructor doesn't say so. You can't tell from the signature what a class needs.
When testing, you can't substitute a different Logger. The class hard-couples to the singleton.
In tests, you want to:
Singletons defeat both. Tests run sequentially; one test's logger state leaks to the next; mocking requires reflection or special test infrastructure.
The naive if (instance == null) { instance = new Logger(); } is racy under multithreaded access. Two threads can create two instances. The fixes (synchronized methods, double-checked locking, holder idiom) add complexity for what should be trivial.
If the singleton has mutable state (most do), every part of the system shares that state. Bugs become "why did changing this here affect that there?"
Singleton A depends on Singleton B; B depends on A. Static initialisation order is hard to control. Either you use lazy initialisation (with the concurrency issues above) or you carefully order initialisation (and pray nothing changes).
Even in test isolation, multiple test threads in one JVM share singletons. Surprises everywhere.
Three different scenarios, often conflated:
Each has a better solution than Singleton.
Construct the single instance once in a "composition root" (the entry point of your application). Pass it where needed via constructor parameters or a DI container.
public class Application {
public static void main(String[] args) {
// Composition root
Logger logger = new ConsoleLogger();
Database db = new PostgresDatabase(config.dbUrl);
UserService users = new UserService(db, logger);
OrderService orders = new OrderService(db, logger, users);
// ... etc
new HttpServer(orders, users).start();
}
}
Properties:
DI containers (Spring, Guice, Dagger, .NET's built-in DI, NestJS) automate this for larger applications. They're "singleton machinery" done correctly — single instances, lifecycle management, automatic injection.
Specific narrow cases:
Even here, modern frameworks usually handle these via DI lifecycle hooks. True hand-rolled Singleton in 2026 is a code smell.
Singleton is common; not necessary.
public class UserService {
private final Logger logger;
public UserService(Logger logger) {
this.logger = logger;
}
public void createUser(...) {
logger.info("Creating user");
}
}
Tests substitute a test logger. Production wires the real one. No singleton.
Load once at startup; pass around.
class Config { /* read-only fields */ }
class Application {
public static void main(String[] args) {
Config config = ConfigLoader.load();
// Pass `config` to components that need it.
}
}
Alternative: an immutable record / data class for config; no singleton needed.
Constructed once at startup; injected into components.
DataSource dataSource = HikariDataSource.create(config);
UserRepository repo = new UserRepository(dataSource);
Spring / similar frameworks handle this with bean lifecycle = "singleton scope" — but the user-facing pattern is DI, not Singleton.
Most HTTP client libraries are designed to be reused (connection pool inside). One instance per service / endpoint, injected.
Same as DB connection pool. Construct once; inject.
Spring's bean scopes:
singleton (default) — one instance per ApplicationContext.prototype — new instance per injection.The Spring singleton scope solves the "single instance" requirement of the GoF Singleton without the static-method access problem. Beans are injected; tests substitute; concurrent initialisation is handled by the container.
Result: most applications using DI have de-facto singletons (one DataSource, one Logger, one HTTP client) without using the Singleton pattern.
If for some reason you can't use DI:
// Holder idiom — thread-safe, lazy
public class Singleton {
private Singleton() {}
private static class Holder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE;
}
}
The holder pattern handles concurrency cleanly via JVM class-loading semantics. Avoids double-checked locking complexity.
For Kotlin, object Singleton { ... } is the language-level singleton.
For Python, a module is implicitly a singleton (loaded once); functions and module-level state work.
For most languages, prefer module-level / static functions over Singleton classes when you genuinely need a single instance with no state.
For new code in 2026:
When you encounter a Singleton in old code, gradually convert to DI. Add a constructor that takes the dependency; have the static getInstance() delegate to a configured instance; eventually retire the static.