Learn the Singleton Design Pattern in Java — what problem it solves, how it works, different implementation approaches, thread safety concerns, common pitfalls, and where Singleton is actually useful in modern software development.
In the previous article, we explored the three major Gang of Four (GoF) Design Pattern categories:
- Creational Patterns
- Structural Patterns
- Behavioral Patterns
Now it’s time to begin our deep dive into Creational Design Patterns.
And there is no better place to start than the most famous — and arguably most controversial — design pattern in Java:
Singleton Pattern.
Almost every Java developer has written one.
Many frameworks use it.
Many engineers misuse it.
Let’s understand what Singleton actually is, what problem it solves, and why blindly applying it can create long-term architectural problems.
What Problem Does Singleton Solve?
Before learning the pattern, understand the problem.
Sometimes an application should have exactly one instance of a particular object.
Examples:
- Configuration Manager
- Logging Service
- Cache Manager
- Application Metrics Registry
- Shared Resource Controller
Suppose you create multiple configuration managers accidentally.
ConfigurationManager #1
ConfigurationManager #2
ConfigurationManager #3
Now problems emerge:
- inconsistent configuration state
- duplicated memory usage
- synchronization challenges
- unpredictable behavior
We want:
One Application
│
▼
One Shared Object Instance
That is the purpose of the Singleton Pattern.
What is Singleton Pattern?
Singleton is a Creational Design Pattern that ensures:
- Only one instance exists
- Global access point is provided
Simple definition:
“Create one object for the entire application lifecycle.”
Naive Object Creation Problem
Normal Java object creation:
public class DatabaseConfig {
}
Usage:
DatabaseConfig config1 =
new DatabaseConfig();
DatabaseConfig config2 =
new DatabaseConfig();
Result:
Multiple independent instances created.
Not Singleton behavior.
Basic Singleton Implementation
A traditional Singleton has:
✔ Private constructor
✔ Static instance variable
✔ Public accessor method
Example:
public class Singleton {
private static Singleton instance =
new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
Usage:
Singleton s1 =
Singleton.getInstance();
Singleton s2 =
Singleton.getInstance();
Verification:
System.out.println(s1 == s2);
Output:
true
Only one object exists.
How Singleton Works Internally
Let’s break down what happens.
Step 1 — Class Loading
JVM loads the class.
Static field gets initialized.
private static Singleton instance =
new Singleton();
Step 2 — Object Created Once
JVM creates exactly one object.
Step 3 — Constructor Blocked
Constructor is private.
This prevents:
new Singleton();
External code cannot create additional instances.
Step 4 — Shared Access
Consumers retrieve instance via:
Singleton.getInstance();
All callers receive the same object.
Eager Initialization Singleton
The previous implementation is called:
Eager Initialization
Because object creation occurs during class loading.
Example:
public class Logger {
private static final Logger INSTANCE =
new Logger();
private Logger() {
}
public static Logger getInstance() {
return INSTANCE;
}
}
Advantages
- Simple implementation
- Thread safe
- JVM handles initialization
Drawbacks
Object created even if never used.
Potential memory waste.
Lazy Initialization Singleton
What if object creation is expensive?
We can delay creation until first usage.
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if(instance == null)
instance = new Singleton();
return instance;
}
}
This is called:
Lazy Initialization.
Object gets created only when needed.
The Thread Safety Problem
Lazy Singleton looks great.
Until concurrency arrives.
Suppose two threads execute simultaneously.
Initial state:
instance = null
Thread-1:
Checks null → TRUE
Thread-2:
Checks null → TRUE
Both create objects.
Result:
Singleton Broken.
Two instances now exist.
Classic concurrency bug.
Thread-Safe Singleton Using synchronized
One simple fix:
public class Singleton {
private static Singleton instance;
private Singleton() {
}
public static synchronized
Singleton getInstance() {
if(instance == null)
instance =
new Singleton();
return instance;
}
}
Now only one thread enters at a time.
Problem solved.
Right?
Partially.
Performance Tradeoff
synchronized introduces locking overhead.
Every call pays synchronization cost.
Even after initialization is complete.
For high-traffic applications, this may become inefficient.
Double-Checked Locking Singleton
A more optimized approach:
public class Singleton {
private static volatile
Singleton instance;
private Singleton() {
}
public static Singleton getInstance() {
if(instance == null) {
synchronized(Singleton.class) {
if(instance == null) {
instance =
new Singleton();
}
}
}
return instance;
}
}
This technique is called:
Double-Checked Locking (DCL).
How it works:
Check #1 → avoid unnecessary locking
Check #2 → ensure thread safety
Why volatile Matters
Without volatile, JVM instruction reordering can create partially initialized objects.
volatile guarantees visibility and safe publication.
This is one of the most misunderstood Singleton details in Java.
Best Singleton Approach: Enum Singleton
Joshua Bloch popularized a cleaner solution.
public enum Singleton {
INSTANCE;
public void execute() {
System.out.println(
"Singleton Running");
}
}
Usage:
Singleton.INSTANCE.execute();
Why Enum Singleton Is Preferred
Advantages:
✔ Thread safe
✔ Serialization safe
✔ Reflection resistant
✔ Minimal code
✔ JVM guaranteed correctness
This is often considered the safest modern implementation.
Real-World Examples of Singleton
Singleton is everywhere.
You already use it daily.
Java Runtime
Example:
Runtime runtime =
Runtime.getRuntime();
One JVM runtime instance.
Spring Beans
By default:
@Service
@Component
@Repository
are typically Singleton scoped.
Spring creates one shared instance.
Logger Objects
Common usage:
private static final Logger log =
LoggerFactory.getLogger(
MyClass.class);
Shared application logging.
Cache Managers
Applications often maintain:
- single cache registry
- shared metrics collector
- centralized configuration store
Natural Singleton candidates.
Common Singleton Mistakes
Singleton is powerful.
But frequently abused.
Mistake 1 — Using Singleton Everywhere
Not every object should be Singleton.
Bad example:
OrderService
PaymentService
UserService
Everything Singleton
This creates hidden global state.
Mistake 2 — Ignoring Thread Safety
Lazy Singleton without synchronization:
Dangerous.
Especially in multi-threaded systems.
Mistake 3 — Hidden Global Dependencies
Singletons can behave like global variables.
Testing becomes harder.
Coupling increases.
Mistake 4 — Mutable Shared State
Suppose:
private int counter;
inside Singleton.
Multiple threads modify shared data.
Now concurrency problems appear.
When Should You Use Singleton?
Good use cases:
✔ Configuration Manager
✔ Logging Infrastructure
✔ Shared Resource Pools
✔ Metrics Registry
✔ Application Runtime Services
Avoid Singleton when:
❌ objects require independent state
❌ heavy testing isolation is needed
❌ hidden global dependencies become problematic
Singleton vs Dependency Injection
Modern frameworks changed design practices.
Spring often removes the need for manual Singleton creation.
Instead of:
Singleton.getInstance()
Spring uses:
@Autowired
private PaymentService service;
Container manages lifecycle.
Cleaner.
More testable.
Less global state.
Quick Comparison of Singleton Approaches
| Approach | Thread Safe | Lazy | Recommended |
|---|---|---|---|
| Basic Singleton | Yes | No | Simple |
| Lazy Singleton | No | Yes | Risky |
| synchronized Singleton | Yes | Yes | Moderate |
| Double-Checked Locking | Yes | Yes | Advanced |
| Enum Singleton | Yes | No | Preferred |
Final Thoughts
Singleton is one of the most widely known design patterns because the underlying problem is common:
Sometimes an application truly needs exactly one shared instance.
But Singleton is also one of the most misused patterns in software engineering.
Used carefully, it simplifies shared resource management.
Used carelessly, it becomes a source of:
- hidden coupling
- global mutable state
- testing challenges
- concurrency bugs
Understanding when to use Singleton — and when not to — is more important than memorizing the implementation.
In the next article, we’ll continue our Creational Design Pattern deep dive with another extremely practical pattern: