Singleton Design Pattern in Java

This article explains Singleton design pattern in java with UML class diagram. It then takes an example scenario in java and explains it with class diagram and code.
Introduction Singleton Design Pattern is a creational design pattern among the Gang Of Four(GOF) Design PatternsClick to read: Gang Of Four(GOF) Design Patterns. Being a creational design pattern, the Singleton pattern deals with how objects of a class are created - in this case, by making sure that only one object of the class is ever created. What is Singleton Design Pattern The Singleton design pattern ensures that a class has only one instance, and provides a global point of access to that instance. The class itself controls its instantiation: its constructor is private, so no other code can create objects of it, and a static method returns the one instance, creating it if necessary. Every part of the application which asks for the object gets the same instance. Scenarios in which Singleton Design Pattern can be used
  1. Exactly one instance must exist: When the system needs exactly one object of a class to coordinate actions across the whole application - such as a configuration registry, a cache, a connection pool, or the application's logging service - and having two would cause inconsistent behaviour.
  2. The instance must be accessible from many places: When many unrelated parts of the application need the same object, and passing it to each of them would be impractical.
  3. The instance is expensive to create: When creating the object is costly - it loads files, opens connections or builds large data structures - so it should be created once, and ideally only when it is first needed.
Class Diagram for Singleton Design Pattern
Singleton Design Pattern Class Diagram
Explanation of Singleton Design Pattern's Class Diagram
  • Singleton holds its only instance in a private static field, instance.
  • Its constructor, Singleton(), is private, so no other class can create an object of it with new.
  • The public static method getInstance() is the global access point. It returns the instance, creating it on the first call if it does not exist yet.
  • businessMethod() stands for the class's actual responsibilities, which callers use through the instance.
  • Client never creates a Singleton; it always calls Singleton.getInstance() and so always works with the same object.
Singleton Design Pattern in Java - Class Diagram
Singleton Design Pattern in Java Class Diagram
The example implements an application configuration registry, AppConfig, which loads the application's settings once and makes them available everywhere. It uses the initialization-on-demand holder idiom, which gives a lazily created, thread-safe singleton without any synchronization.
Code for the classes shown in Java Example's Class Diagram
AppConfig.java
//AppConfig.java
import java.util.Map;

public final class AppConfig {
 private final Map<String, String> settings;

 private AppConfig() {
  // Expensive work done only once - e.g. reading a configuration file
  System.out.println("Loading configuration...");
  settings = Map.of("app.name", "JavaBrahman", "db.url", "jdbc:postgresql://localhost/jb", "cache.size", "500");
 }

 // The JVM loads Holder - and creates INSTANCE - only when getInstance() is first called
 private static class Holder {
  static final AppConfig INSTANCE = new AppConfig();
 }

 public static AppConfig getInstance() {
  return Holder.INSTANCE;
 }

 public String get(String key) {
  return settings.get(key);
 }
}
AppClient.java (with the main() method)
//AppClient.java
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class AppClient {
 public static void main(String[] args) throws Exception {
  System.out.println("Application started");
  AppConfig config = AppConfig.getInstance();          // first call: the instance is created now
  System.out.println("App name: " + config.get("app.name"));

  AppConfig again = AppConfig.getInstance();           // later calls: the same instance
  System.out.println("Same instance: " + (config == again));

  // 100 threads asking at the same time still get one instance
  Set<AppConfig> seen = ConcurrentHashMap.newKeySet();
  try (ExecutorService pool = Executors.newFixedThreadPool(16)) {
   for (int i = 0; i < 100; i++) {
    pool.submit(() -> seen.add(AppConfig.getInstance()));
   }
  }
  System.out.println("Distinct instances seen by 100 tasks: " + seen.size());
 }
}
 OUTPUT on running AppClient.java
Application started
Loading configuration...
App name: JavaBrahman
Same instance: true
Distinct instances seen by 100 tasks: 1
Explanation of Java Example's Class Diagram & Code
  • AppConfig is the singleton. Its constructor is private, and its only instance lives in the static field INSTANCE of the nested class Holder.
  • "Loading configuration..." is printed after "Application started": the JVM initializes a class only when it is first used, so Holder - and the AppConfig object - is created on the first call to getInstance(). This is lazy initialization, without any extra code.
  • Class initialization in the JVM is guaranteed to happen exactly once, even when several threads trigger it at the same time. That is why 100 concurrent tasks all got the same object, without synchronized or volatile.
  • AppClient never calls new AppConfig() - which would not compile - and always works with the one shared instance.
Other ways to implement a Singleton in Java The holder idiom is one of several common implementations. Each has trade-offs -
Eager, synchronized lazy, double-checked locking and enum singletons
//SingletonVariants.java
public class SingletonVariants {
 // 1. Eager: created when the class loads, even if never used. Simple and thread-safe.
 static final class EagerSingleton {
  private static final EagerSingleton INSTANCE = new EagerSingleton();
  private EagerSingleton() { }
  static EagerSingleton getInstance() { return INSTANCE; }
 }

 // 2. Lazy with a synchronized method: thread-safe, but every call pays for the lock.
 static final class SynchronizedSingleton {
  private static SynchronizedSingleton instance;
  private SynchronizedSingleton() { }
  static synchronized SynchronizedSingleton getInstance() {
   if (instance == null) instance = new SynchronizedSingleton();
   return instance;
  }
 }

 // 3. Double-checked locking: lazy, and locks only on the first call. 'volatile' is essential.
 static final class DclSingleton {
  private static volatile DclSingleton instance;
  private DclSingleton() { }
  static DclSingleton getInstance() {
   DclSingleton local = instance;
   if (local == null) {
    synchronized (DclSingleton.class) {
     local = instance;
     if (local == null) instance = local = new DclSingleton();
    }
   }
   return local;
  }
 }

 // 4. Enum: the JVM guarantees one instance, even against reflection and serialization.
 enum EnumSingleton {
  INSTANCE;
  String greet() { return "Hello from the enum singleton"; }
 }

 public static void main(String[] args) throws Exception {
  System.out.println("Eager: " + (EagerSingleton.getInstance() == EagerSingleton.getInstance()));
  System.out.println("Synchronized: " + (SynchronizedSingleton.getInstance() == SynchronizedSingleton.getInstance()));
  System.out.println("Double-checked: " + (DclSingleton.getInstance() == DclSingleton.getInstance()));
  System.out.println(EnumSingleton.INSTANCE.greet());

  // Reflection can call a private constructor of a class-based singleton...
  var ctor = EagerSingleton.class.getDeclaredConstructor();
  ctor.setAccessible(true);
  System.out.println("Reflection made a second EagerSingleton: " + (ctor.newInstance() != EagerSingleton.getInstance()));
  // ...but not of an enum
  try {
   var enumCtor = EnumSingleton.class.getDeclaredConstructor(String.class, int.class);
   enumCtor.setAccessible(true);
   enumCtor.newInstance("SECOND", 1);
  } catch (IllegalArgumentException e) {
   System.out.println("Reflection on the enum: " + e.getMessage());
  }
 }
}
 OUTPUT on running SingletonVariants.java
Eager: true
Synchronized: true
Double-checked: true
Hello from the enum singleton
Reflection made a second EagerSingleton: true
Reflection on the enum: Cannot reflectively create enum objects
  • Eager initialization is the simplest and is thread-safe, but creates the instance even if it is never used.
  • A synchronized getInstance() is lazy and correct, but every call acquires a lock.
  • Double-checked locking locks only while the instance is being created. The field must be volatile; without it, another thread could see a partly constructed object.
  • The enum singleton, recommended in Effective Java, is the most robust: the JVM creates exactly one instance, serialization cannot create a second one, and - as the output shows - reflection cannot call an enum's constructor, while it can create a second instance of the class-based EagerSingleton.
Note - A singleton is global state, which makes code harder to test, since tests cannot easily substitute it. In applications using a dependency injection framework such as Spring, prefer a single bean managed by the container, which gives the same "one instance" guarantee while keeping classes testable.
Summary In the above tutorial we understood what is Singleton design pattern and the main scenarios in which it is applicable. We then looked at the UML class diagram for Singleton Design Pattern & its explanation, a Java use case implementing the Singleton pattern with the holder idiom, with its class diagram, code and output, followed by an explanation of both the class diagram & code. Lastly, we compared the other common implementations - eager, synchronized, double-checked locking and enum. This concludes the tutorial on Singleton design pattern.