DEV Community

Priyank Bhardwaj
Priyank Bhardwaj

Posted on

Singleton Design Pattern

Introduction to Creational Design Patterns

Creational design patterns deal with how objects are created. Instead of instantiating objects directly using the new keyword, creational patterns offer more flexible and decoupled ways of object creation.

They help:

  • Make your code more modular and testable
  • Adhere to SOLID principles (especially Single Responsibility and Dependency Inversion)
  • Reduce the risk of tight coupling between classes

🔍 Popular Creational Patterns:

  1. Singleton
  2. Factory Method
  3. Abstract Factory
  4. Builder
  5. Prototype

Singleton Design Pattern

What is Singleton?

Singleton is a design pattern that ensures only one instance of a class exists in the JVM and provides a global point of access to it.

✅ Use Cases

  • Logging
  • Caching
  • Configuration settings
  • Database connection pools

Real-world Analogy

Think of your home’s power supply system. You may have multiple appliances — TV, AC, refrigerator, washing machine — all needing electricity. Instead of having a separate generator for each appliance (which would be expensive and wasteful), you have one centralized power supply (say, a main circuit box or grid connection) that serves all appliances.

This centralized power supply acts like a Singleton — a single, globally accessible instance that is shared across the application (in this case, your home). It’s created once (your electricity setup), and everything else depends on it without duplicating it.


🛠️ Different Types of Singleton Implementations

1. Eager Initialization (Thread-safe)


public class EagerSingleton {
    private static final EagerSingleton instance = new EagerSingleton();
    private EagerSingleton() {}
    public static EagerSingleton getInstance() {
        return instance;
    }
}

Enter fullscreen mode Exit fullscreen mode

✅ Why it’s thread-safe:

  • The instance is created at the time of class loading.
  • Class loading in Java is thread-safe by specification (handled by JVM’s class loader).
  • No multiple threads can create separate instances because the instance is already there before any thread accesses it.

⚠️ Downside:
Instance is created even if it is never used, wasting resources.

2. Lazy Initialization (❌ Not Thread-safe)


public class LazySingleton {
    private static LazySingleton instance;
    private LazySingleton() {}
    public static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton();
        }
        return instance;
    }
}

Enter fullscreen mode Exit fullscreen mode

❌ Why it’s not thread-safe:

  • If two threads call getInstance() at the same time when instance is null, both can create a new instance.
  • This defeats the purpose of Singleton.

Example scenario:

  • Imagine two users accessing a Logger at the same time.
  • Both create a Logger instance because the first thread didn’t finish before the second started.
  • Now you have two loggers, not one!

3. Bill Pugh Singleton (✅ Thread-safe, Lazy-loaded, No synchronization overhead)


public class BillPughSingleton {
    private BillPughSingleton() {}
    private static class SingletonHelper {
        private static final BillPughSingleton INSTANCE = new BillPughSingleton();
    }
    public static BillPughSingleton getInstance() {
        return SingletonHelper.INSTANCE;
    }
}

Enter fullscreen mode Exit fullscreen mode

✅ Why it’s thread-safe:

  • The instance is created only when the nested static class is loaded.
  • The nested class SingletonHelper is not loaded until getInstance() is called.
  • Class loading is inherently thread-safe in Java.
  • No synchronized block needed, so there’s no performance penalty.

🔐 JVM guarantees that the class initialization phase is serial and thread-safe, even for static inner classes.

✅ Bonus:
Combines lazy initialization (only create when needed) with thread safety and no performance hit from synchronized.

🌍 Real-Life Singleton Implementation Example

Now we will put our knowledge in effect and implement the singleton pattern to establish a database connection. This is a good use case as we would not want to create multiple connections to the same database.


import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class DatabaseConnectionManager {
    // Database properties come from a properties file, hardcoded just for tutorial purpose.
    private static final String DB_URL = "jdbc:mysql://localhost:3306/mydb";
    private static final String DB_USER = "root";
    private static final String DB_PASSWORD = "password";

    private Connection connection;

    // Private constructor to prevent instantiation
    private DatabaseConnectionManager() {
        try {
            connection = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);
            System.out.println("Database connection established.");
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    // Static nested helper class for lazy-loaded singleton
    private static class SingletonHelper {
        private static final DatabaseConnectionManager INSTANCE = new DatabaseConnectionManager();
    }

    // Global access point
    public static DatabaseConnectionManager getInstance() {
        return SingletonHelper.INSTANCE;
    }

    public Connection getConnection() {
        return connection;
    }
}

Enter fullscreen mode Exit fullscreen mode

🧪 Usage Example

public class Main {
    public static void main(String[] args) {
        DatabaseConnectionManager manager1 = DatabaseConnectionManager.getInstance();
        DatabaseConnectionManager manager2 = DatabaseConnectionManager.getInstance();

        System.out.println(manager1 == manager2); // true

        Connection conn = manager1.getConnection();
        // Use the connection for queries...
    }
}

Enter fullscreen mode Exit fullscreen mode

🔐 Benefits of This Approach

  • Thread-safe without synchronization
  • Lazy-initialized only when getInstance() is called
  • Guarantees a single DB connection manager, avoiding resource leaks
  • Can be extended later for connection pooling (e.g., HikariCP or Apache DBCP)

⚠️ Notes

  • Never keep a single DB Connection open forever in production.
  • This example is for educational/demo purposes. In real-world apps:
  • Use connection pools
  • Manage resources with try-with-resources
  • Externalize DB configs (.properties or .env files)

Singleton in Real Frameworks

  • Spring Framework: Beans are Singleton by default (@scope("singleton"))
  • Logger Frameworks: Most use Singleton internally (e.g., Log4j, SLF4J)
  • Java Runtime: Runtime.getRuntime() is a classic Singleton

Pros and Cons

✅ Pros

  • Controlled instance creation
  • Saves memory (only one object)
  • Good for shared resources

❌ Cons

  • Hard to test (tight coupling and global state)
  • Difficult in multithreaded scenarios if not implemented correctly
  • Violates Single Responsibility Principle (manages its own lifecycle)

Best Practices

  • Use Singleton when only one instance is truly needed
  • Prefer Bill Pugh or Enum-based implementation in multithreaded apps
  • Avoid using Singleton just to share data — consider Dependency Injection instead

This is Part 1 of the Java Design Patterns Series.

If you find it insightful, please share your feedback. Also let me know if you have used singleton pattern in your projects.

Next Up: Factory Method Pattern – Create objects without exposing the instantiation logic!

Top comments (0)