Back to Creational: Singleton, Factory, Builder, Prototype
Q12Question 12 of 12

Design using the Singleton pattern

Key Concepts
  • Singleton guarantees exactly one shared instance, created safely and accessed globally through a static method.
  • The initialization-on-demand holder idiom is lazy and thread-safe without explicit locking because the JVM synchronises class loading.
  • The enum singleton is the simplest and safest approach, immune to reflection and serialisation attacks.
  • Connection pools and configuration managers are textbook Singleton use cases because duplicates waste resources or cause inconsistency.
  • Singletons can become hidden global state that complicates testing, so prefer dependency injection for testability.
  • Double-checked locking requires a `volatile` field to be correct and is error-prone compared to the holder idiom.

Definition

Singleton guarantees exactly one shared instance, created safely and accessed globally through a static method.
  • 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.

JAVA
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.

JAVA
// 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.

JAVA
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.

JAVA
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.

JAVA
package com.bank.config;

public enum ConfigManager {
    INSTANCE;

    public String getDbUrl() {
        return "jdbc:postgresql://localhost:5432/national_bank";
    }
}
JAVA
// 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.

JAVA
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.

JAVA
// 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.

JAVA
// 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.

JAVA
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'");
    }
}
TEXT
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.

JAVA
// 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.

JAVA
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.

JAVA
// 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

DIAGRAM
  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
DIAGRAM
  ┌──────────────────────────────────────────┐
  │          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.

---

Share
Use ← → arrow keys to navigate