Definition
- Singleton – a creational pattern that ensures a class has exactly one instance across the entire application and provides a global access point to it.
- Private constructor – prevents external instantiation, so the only way to get the instance is through the static
getInstance()method. - Lazy vs eager – eager creates the instance at class-load time; lazy defers creation until first access to save resources.
- Thread safety – in a multi-threaded environment, two threads calling
getInstance()simultaneously must not create two instances. - Use cases – shared, expensive, or coordinating resources such as a configuration manager, a logger, or a database connection pool.
Think of the single master vault key at National Bank of India. There is exactly one, everyone who needs vault access uses that same key, and the bank never cuts duplicates. If Rajesh and Priya both need the configuration or the connection pool, they get the same shared object, because having several would waste resources and cause inconsistency.
Understanding the Concepts
What is the Singleton pattern?
The Singleton pattern ensures a class has exactly one instance across the entire application and provides a global access point to it. The class itself controls its instantiation by making its constructor private and exposing a static method that returns the single instance.
package com.bank.config;
public class ConfigManager {
private static final ConfigManager INSTANCE = new ConfigManager();
private ConfigManager() {
// Load configuration from properties file
}
public static ConfigManager getInstance() {
return INSTANCE;
}
public String getProperty(String key) {
// Return configuration value
return System.getProperty(key, "default");
}
}
Why does the Singleton pattern exist?
Some resources must be shared and coordinated across the entire application. A database connection pool is the classic example: opening connections is expensive, the pool must be shared so connections are reused, and multiple pools would quickly exhaust the database.
Without a Singleton, any class could create its own connection pool, leading to resource waste and inconsistency. The Singleton guarantees one pool, one configuration, or one logger for the whole process.
// Without Singleton: multiple pools exhaust the database
ConnectionPool pool1 = new ConnectionPool(); // 10 connections
ConnectionPool pool2 = new ConnectionPool(); // 10 more connections
// Now 20 connections instead of 10, and they are uncoordinated
How do you implement Singleton safely in Java?
There are several approaches, each with different trade-offs for thread safety, laziness, and simplicity.
Eager initialisation
The instance is created at class-load time. It is thread-safe because the JVM guarantees that static initialisers run exactly once, but it is not lazy.
package com.bank.config;
public class ConfigManager {
private static final ConfigManager INSTANCE = new ConfigManager();
private ConfigManager() { }
public static ConfigManager getInstance() { return INSTANCE; }
}
Initialization-on-demand holder (recommended)
The instance lives in a private static nested class. This is both lazy and thread-safe without any explicit locking, because the JVM loads the holder class only when getInstance() is first called, and class loading is synchronised by the JVM.
package com.bank.config;
public class ConfigManager {
private ConfigManager() { }
private static class Holder {
static final ConfigManager INSTANCE = new ConfigManager();
}
public static ConfigManager getInstance() {
return Holder.INSTANCE;
}
}
Enum singleton (safest)
The enum approach is the simplest and safest. It is immune to reflection attacks and handles serialisation correctly by default.
package com.bank.config;
public enum ConfigManager {
INSTANCE;
public String getDbUrl() {
return "jdbc:postgresql://localhost:5432/national_bank";
}
}
// Usage
String url = ConfigManager.INSTANCE.getDbUrl();
How does double-checked locking work?
Double-checked locking checks the instance twice: once without a lock and once with a lock. It requires the instance field to be volatile so that the write to the field is visible to all threads. Without volatile, a thread can see a partially constructed object.
package com.bank.pool;
import java.math.BigDecimal;
public class ConnectionPool {
private static volatile ConnectionPool instance;
private ConnectionPool() {
// Open initial database connections
}
public static ConnectionPool getInstance() {
if (instance == null) { // First check (no lock)
synchronized (ConnectionPool.class) {
if (instance == null) { // Second check (with lock)
instance = new ConnectionPool();
}
}
}
return instance;
}
public void executeQuery(String sql) {
// Borrow connection, execute, return connection
}
}
The holder idiom is preferred because it achieves the same result with less complexity and no chance of forgetting volatile.
What are common mistakes with Singleton?
The first mistake is forgetting volatile in double-checked locking. Without it, the JVM may reorder the write to the instance field and the constructor execution, so another thread can see a half-initialised object.
// Wrong: missing volatile
private static ConnectionPool instance; // Another thread can see
// a partially constructed object
// Correct: volatile ensures visibility
private static volatile ConnectionPool instance;
The second mistake is not protecting against reflection. A determined caller can use reflection to invoke the private constructor and create a second instance.
// Attack: reflection breaks the Singleton
Constructor<ConfigManager> c = ConfigManager.class
.getDeclaredConstructor();
c.setAccessible(true);
ConfigManager second = c.newInstance(); // Now there are two instances
The enum singleton is immune to this because the JVM prevents reflective construction of enum instances.
When should you use Singleton?
Use Singleton for genuinely process-wide resources where having more than one instance would cause problems: a connection pool, a configuration manager, or a shared cache. These are expensive to create, must be shared, and must be coordinated.
Do not use Singleton for objects that could reasonably have multiple instances, for objects that need different configurations in tests, or as a lazy alternative to proper dependency injection.
package com.bank.demo;
import com.bank.config.ConfigManager;
import com.bank.pool.ConnectionPool;
import java.math.BigDecimal;
public class SingletonDemo {
public static void main(String[] args) {
// Both calls return the same instance
ConfigManager config1 = ConfigManager.getInstance();
ConfigManager config2 = ConfigManager.getInstance();
System.out.println("Same config? " + (config1 == config2));
ConnectionPool pool = ConnectionPool.getInstance();
pool.executeQuery("SELECT balance FROM accounts WHERE id = 'SAV2001'");
}
}
Same config? true
How does Singleton interact with dependency injection?
In modern systems, dependency injection frameworks like Spring manage bean scopes. A Spring bean with @Scope("singleton") (the default) behaves like a Singleton, with one instance per application context, but it is managed by the framework rather than by the class itself.
This gives the same "one instance" guarantee while keeping the code testable, because in a test you can inject a mock. A true Singleton with a private constructor and a static getInstance() method is harder to mock.
// Spring-managed singleton: testable
@Service
public class AccountService {
private final ConnectionPool pool;
public AccountService(ConnectionPool pool) {
this.pool = pool; // Injected, can be mocked in tests
}
}
What about serialisation?
If a Singleton implements Serializable, deserialisation creates a new instance by default, breaking the Singleton guarantee. The fix is to implement readResolve() to return the existing instance.
package com.bank.config;
import java.io.Serializable;
public class ConfigManager implements Serializable {
private static final long serialVersionUID = 1L;
private static final ConfigManager INSTANCE = new ConfigManager();
private ConfigManager() { }
public static ConfigManager getInstance() { return INSTANCE; }
// Prevent deserialisation from creating a second instance
private Object readResolve() {
return INSTANCE;
}
}
The enum singleton handles this automatically, which is another reason it is the safest approach.
What should a senior engineer know?
Singletons are easy to overuse and become hidden global state. In production systems, Pankaj prefers dependency injection to hand out a single managed instance, which gives the "one instance" benefit while keeping the code testable. A true Singleton is justified only for genuinely process-wide resources like a connection pool.
The holder idiom and the enum singleton are the two recommended approaches. Double-checked locking is error-prone. Eager initialisation is acceptable when the resource is always needed.
// Senior approach: DI-managed singleton with testability
@Configuration
public class BankConfig {
@Bean
public ConnectionPool connectionPool() {
return new ConnectionPool("jdbc:postgresql://localhost/national_bank");
}
}
// Spring creates exactly one ConnectionPool and injects it everywhere
// In tests, you can @MockBean ConnectionPool for isolation
Visual Overview
Thread A Thread B
│ │
│ getInstance() │ getInstance()
▼ ▼
┌────────────────────────────────────────┐
│ Holder class not loaded yet │
│ │
│ First call triggers class loading: │
│ ┌─────────────────────────────┐ │
│ │ class Holder { │ │
│ │ static final INSTANCE │ │
│ │ = new ConfigManager() │ │
│ │ } │ │
│ └─────────────────────────────┘ │
│ JVM synchronises class loading │
│ Only one instance is ever created │
└────────────────────────────────────────┘
│
▼
Returns same INSTANCE to both threads
┌──────────────────────────────────────────┐ │ Singleton Approaches │ │ │ │ Eager Holder Enum │ │ ┌─────┐ ┌─────┐ ┌─────┐ │ │ │ static│ │ lazy │ │ safe │ │ │ │ final │ │ + safe│ │ + lazy│ │ │ │ field │ │ + no │ │ + no │ │ │ │ │ │ locks │ │ reflect│ │ │ └─────┘ └─────┘ └─────┘ │ │ Simple Recommended Safest │ └──────────────────────────────────────────┘
Key Takeaway
The Singleton pattern guarantees that a class has exactly one instance across the entire application and provides a single global access point to it. Rajesh uses it for genuinely process-wide resources such as a configuration manager, a logger, or a database connection pool, where having several copies would waste resources or cause inconsistency.
The preferred implementation in Java is the initialization-on-demand holder idiom, where the single instance lives in a private static nested holder class. This is both lazy, because the holder is not loaded until the first access, and thread-safe, because the JVM synchronises class loading automatically, so you get correct behaviour without writing any locks. The enum singleton is an equally safe alternative and is immune to both reflection and serialisation attacks, while the older double-checked locking approach is error-prone and requires a volatile field to be correct.
The connection pool is the textbook justification for a Singleton. Opening database connections is expensive, and the pool must be shared and coordinated so connections are reused rather than exhausted. Multiple pools would quickly overwhelm the database. The configuration manager is similar: it is loaded once at startup, and every service must read the same consistent values.
The senior caution Pankaj always adds is that Singletons are easy to overuse and can become hidden global state that makes code hard to test. In modern systems, dependency injection lets the framework create and hand out a single managed instance, which gives the one-instance guarantee while keeping the code mockable in tests. At National Bank of India, true Singletons are reserved for the connection pool and configuration, and dependency injection handles everything else for testability.
Remember one sentence: Singleton ensures one shared instance globally, and the holder idiom or enum singleton are the safest Java implementations.
---