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:
- Singleton
- Factory Method
- Abstract Factory
- Builder
- 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;
}
}
✅ 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;
}
}
❌ 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;
}
}
✅ 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;
}
}
🧪 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...
}
}
🔐 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)