Ensure a class has only one instance and provide a global point of access to it.
The Singleton pattern restricts the instantiation of a class to a single object and provides a global access point to that instance. It ensures that no matter how many times you ask for the object, you always get the same one.
It solves two problems at once: controlling how many instances of a class exist, and providing a single point of access. This is one of the simplest yet most debated patterns in software engineering.
Imagine a logging service, a thread pool, or a configuration manager. If your application creates multiple instances of these, you'll have inconsistent state — two loggers writing to different files, two config objects with different values, two thread pools competing for resources.
You need a way to guarantee that only one instance ever exists, and that it is easily accessible throughout the codebase — without passing it around as a parameter everywhere.
The Singleton pattern has two components: make the default constructor private (so no one can call new on it), and provide a static creation method that acts as a constructor but returns the cached instance.
getInstance() method. Has a private constructor to block external instantiation.// Thread-safe Singleton using double-checked locking public class DatabaseConnection { // volatile ensures visibility across threads private static volatile DatabaseConnection instance; private String url; private String status; // Private constructor — no outside instantiation private DatabaseConnection() { this.url = "jdbc:postgresql://localhost/mydb"; this.status = "connected"; System.out.println("Connection established"); } // Double-checked locking for performance + thread safety public static DatabaseConnection getInstance() { if (instance == null) { synchronized (DatabaseConnection.class) { if (instance == null) { instance = new DatabaseConnection(); } } } return instance; } public String getUrl() { return url; } public void query(String sql) { System.out.println("Executing: " + sql); } } // --- Enum Singleton (best practice, serialization-safe) --- public enum AppConfig { INSTANCE; private String env = "production"; public String getEnv() { return env; } public void setEnv(String env) { this.env = env; } } // --- Client usage --- public class Main { public static void main(String[] args) { DatabaseConnection db1 = DatabaseConnection.getInstance(); DatabaseConnection db2 = DatabaseConnection.getInstance(); System.out.println(db1 == db2); // true — same instance db1.query("SELECT * FROM users"); AppConfig.INSTANCE.setEnv("staging"); System.out.println(AppConfig.INSTANCE.getEnv()); // staging } }
java.lang.Runtime.getRuntime() — JVM runtime environment, exactly one per JVMjava.awt.Desktop.getDesktop() — desktop environment wrapperApplicationContext — bean container, single context per applicationSessionFactory — expensive to create, shared across the appConstructor.setAccessible(true) bypasses the private constructor and creates a second instanceCloneable, clone() produces a second instancenewif (instance != null) throw new RuntimeException("Use getInstance()")readResolve() to return the existing instance instead of the deserialized oneclone() and throw CloneNotSupportedExceptionenum AppConfig { INSTANCE; } to eliminate all these vulnerabilities at oncevolatile + double-checked locking, or initialize in a static initializer block// ❌ BREAKING via Reflection Constructor<DatabaseConnection> ctor = DatabaseConnection.class.getDeclaredConstructor(); ctor.setAccessible(true); DatabaseConnection hack = ctor.newInstance(); // second instance! // ✅ FIX — guard inside constructor private DatabaseConnection() { if (instance != null) throw new RuntimeException("Use getInstance()"); } // ❌ BREAKING via Serialization // ObjectOutputStream writes the instance to disk // ObjectInputStream.readObject() creates a NEW instance // ✅ FIX — readResolve() returns the existing singleton protected Object readResolve() { return getInstance(); } // ✅ BEST FIX — Enum Singleton (immune to all attacks) public enum SafeSingleton { INSTANCE; public void doWork() { /* ... */ } }
getInstance().volatile + double-checked locking for thread safety, or use Enum Singleton.