Singleton Pattern in Java – Explained with Examples

Introduction
In software development, there are often situations where it is required to have a single instance of an object or feature that can serve as a global access point for the entire application. Creating more than one instance might result in inaccurate program behavior or unnecessary overhead, which can lead to application performance degradation.
This is where the Singleton Design Pattern comes into the picture. It is one of the simplest design patterns used to control the creation process, ensuring that only one instance of a class ever exists and providing a way to access that instance across the application.
What is the Singleton Pattern?
Singleton Pattern is a creational design pattern that guarantees a class has only one instance and provides a global point of access to it.
It involves only one class, which is responsible for instantiating itself and ensuring that only one instance is created.
To implement the Singleton pattern, we must prevent external objects from creating instances of the Singleton class. Only the Singleton class should be able to instantiate itself.
Additionally, we need to ensure we provide a global access method for external objects to access the singleton instance.
Class Diagram
In Java, one of the ways to implement the Singleton pattern is:
by making the constructor private
and providing a static method for external access

Implementation
There are several ways to implement the Singleton pattern depending on requirements.
1. Eager Initialization
In this approach, the responsibility of instance creation lies with the JVM and happens at class loading time.
class EagerSingleton {
// instance initialized at the time of class loading
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton() {
// Private constructor to prevent instantiation
System.out.println("EagerSingleton instance created.");
}
public static EagerSingleton getInstance() {
System.out.println("getInstance called.");
return instance;
}
}
public class EagerSingletonExample {
static void main() {
EagerSingleton object1 = EagerSingleton.getInstance();
EagerSingleton object2 = EagerSingleton.getInstance();
System.out.println("Are both instances the same? " + (object1 == object2));
}
}
Explanation
As we can see in the output, the instance is created once when the program runs, and any external objects requiring the instance can use the same instance accessible through a public static method.
Program Output
EagerSingleton instance created.
getInstance called.
getInstance called.
Are both instances the same? true
Pros
Easy to implement
Inherently thread-safe
Cons
- Can result in performance overhead if the instance is never used
2. Lazy Initialization
In this approach, the instance gets created only when it is required for the first time.
class LazySingleton {
// instance gets created only when it is requested for the first time
private static LazySingleton instance;
private LazySingleton() {
// Private constructor to prevent instantiation
System.out.println("LazySingleton instance created.");
}
public static LazySingleton getInstance() {
System.out.println("getInstance called.");
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
public class LazySingletonExample {
static void main() {
LazySingleton object1 = LazySingleton.getInstance();
LazySingleton object2 = LazySingleton.getInstance();
System.out.println("Are both instances the same? " + (object1 == object2));
}
}
Program Output
getInstance called.
LazySingleton instance created.
getInstance called.
Are both instances the same? true
Pros
- Saves resources by creating the instance only when required
Cons
- Not thread-safe

3. Thread-Safe Singleton
We can make the Lazy Singleton implementation thread-safe by synchronizing the static getInstance() method.
class ThreadSafeSingleton {
// instance creation similar to Lazy initialization but with thread safety
private static ThreadSafeSingleton instance;
private ThreadSafeSingleton() {
// Private constructor to prevent instantiation
System.out.println("ThreadSafeSingleton instance created.");
}
public static synchronized ThreadSafeSingleton getInstance() {
System.out.println("getInstance called.");
if (instance == null) {
instance = new ThreadSafeSingleton();
}
return instance;
}
}
public class ThreadSafeSingletonExample {
static void main() {
ThreadSafeSingleton object1 = ThreadSafeSingleton.getInstance();
ThreadSafeSingleton object2 = ThreadSafeSingleton.getInstance();
System.out.println("Are both instances the same? " + (object1 == object2));
}
}
Program Output
getInstance called.
ThreadSafeSingleton instance created.
getInstance called.
Are both instances the same? true
Pros
- Simple and thread-safe
Cons
- Performance overhead due to synchronization
4. Double-Checked Locking
This approach reduces synchronization overhead.
class DoubleCheckedSingleton {
// instance with double-checked locking for thread safety and performance
private static volatile DoubleCheckedSingleton instance;
private DoubleCheckedSingleton() {
// Private constructor to prevent instantiation
System.out.println("DoubleCheckedSingleton instance created.");
}
public static DoubleCheckedSingleton getInstance() {
System.out.println("getInstance called.");
if (instance == null) {
synchronized (DoubleCheckedSingleton.class) {
if (instance == null) {
instance = new DoubleCheckedSingleton();
}
}
}
return instance;
}
}
public class DoubleCheckedSingletonExample {
static void main() {
DoubleCheckedSingleton object1 = DoubleCheckedSingleton.getInstance();
DoubleCheckedSingleton object2 = DoubleCheckedSingleton.getInstance();
System.out.println("Are both instances the same? " + (object1 == object2));
}
}
Program Output
getInstance called.
DoubleCheckedSingleton instance created.
getInstance called.
Are both instances the same? true
Pros
Better performance
Thread-safe
Cons
- Slightly complex implementation

5. Bill Pugh Singleton Implementation
This approach uses a static inner helper class.
class BillPughSingleton {
// Lazy and thread-safe singleton implementation using Bill Pugh's method
private BillPughSingleton() {
// Private constructor to prevent instantiation
System.out.println("BillPughSingleton instance created.");
}
// Inner static helper class responsible for holding the singleton instance
private static class SingletonHelper {
private static final BillPughSingleton instance = new BillPughSingleton();
}
public static BillPughSingleton getInstance() {
System.out.println("getInstance called.");
return SingletonHelper.instance;
}
}
public class BillPughSingletonExample {
static void main() {
BillPughSingleton object1 = BillPughSingleton.getInstance();
BillPughSingleton object2 = BillPughSingleton.getInstance();
System.out.println("Are both instances the same? " + (object1 == object2));
}
}
Program Output
getInstance called.
BillPughSingleton instance created.
getInstance called.
Are both instances the same? true
Pros
Lazy loading
Thread-safe
No synchronization overhead
Cons
- Slightly harder to understand for beginners
6. Static Block Initialization
Similar to eager initialization but allows exception handling.
class StaticBlockSingleton {
// instance created in static block for improved exception handling
private static StaticBlockSingleton instance;
private StaticBlockSingleton() {
// Private constructor to prevent instantiation
System.out.println("StaticBlockSingleton instance created.");
}
// Static block for instance creation
static {
try {
instance = new StaticBlockSingleton();
} catch (Exception exception) {
throw new RuntimeException("Exception occurred in creating singleton instance", exception);
}
}
public static StaticBlockSingleton getInstance() {
System.out.println("getInstance called.");
return instance;
}
}
public class StaticBlockSingletonExample {
static void main() {
StaticBlockSingleton object1 = StaticBlockSingleton.getInstance();
StaticBlockSingleton object2 = StaticBlockSingleton.getInstance();
System.out.println("Are both instances the same? " + (object1 == object2));
}
}
Program Output
StaticBlockSingleton instance created.
getInstance called.
getInstance called.
Are both instances the same? true
Pros
Eager based straightforward implementation.
It is thread safe and also provides exception handling.
Cons
- It can have slight performance overhead as it is eager based loading specially if the object creation is resource-intensive or time-consuming.
7. Enum Singleton
One of the cleanest and safest implementations.
enum Singleton {
// Single instance created when the enum is loaded
INSTANCE;
Singleton() {
System.out.println("Enum Singleton instance created.");
}
public void someMethod() {
System.out.println("someMethod called.");
}
}
public class EnumSingletonExample {
static void main() {
Singleton object1 = Singleton.INSTANCE;
object1.someMethod();
Singleton object2 = Singleton.INSTANCE;
System.out.println("Are both instances the same? " + (object1 == object2));
}
}
Program Output
Enum Singleton instance created.
someMethod called.
Are both instances the same? true
Pros
Thread-safe
Reflection-safe
Serialization-safe
Cons
- Not suitable when lazy initialization is required
Real World Use Cases of Singleton Pattern
Managing shared resources (database connections, thread pools, caches)
Logging services
Configuration settings
Managing application state
When NOT to Use Singleton
When global state can lead to tight coupling
When unit testing becomes difficult
When dependency injection is preferred
Conclusion
In this article, we briefly learned about the Singleton pattern, what it is, and the various implementation approaches available based on use cases. We also looked at the pros and cons of each approach.
The Singleton pattern is fundamental and useful, but it should be used judiciously as it introduces global state management and can make testing and maintenance more challenging. Consider approaches like dependency injection when possible to promote loose coupling and better testability.

