How to Make a Singleton Class in Java
Introduction
Creating a singleton class in java ensures that only one instance of a class exists throughout the application’s lifecycle. This pattern is useful for managing resources such as configuration objects, logging services, or database connections, where a single point of control is required. In this article we will explore the concept of the singleton pattern, examine the different initialization strategies, and provide step‑by‑step instructions for implementing a strong singleton class. By the end, you will have a clear, reusable blueprint that can be applied to any Java project Small thing, real impact..
Some disagree here. Fair enough Easy to understand, harder to ignore..
Understanding the Singleton Pattern
The singleton pattern is a creational design pattern that restricts a class to a single instance and provides a global point of access to that instance. The core requirements are:
- Single Instance – The class must guarantee that only one object is created.
- Global Access – The class must expose a method to retrieve the existing instance.
- Controlled Creation – The constructor should be hidden or restricted to prevent external instantiation.
These requirements translate into a set of implementation choices that affect thread safety, performance, and readability.
Steps to Implement a Singleton Class
Below are the essential steps to create a singleton class in Java, followed by detailed explanations of each step.
1. Declare the Class with a Private Constructor
Prevent external code from invoking the constructor directly.
public class Singleton {
// **Private constructor** – no direct instantiation allowed
private Singleton() {
// optional initialization code
}
}
2. Add a Static Field to Hold the Instance
Use a private static field to store the sole instance.
private static final Singleton INSTANCE = new Singleton();
3. Provide a Public Static Accessor Method
Expose a method that returns the stored instance It's one of those things that adds up..
public static Singleton getInstance() {
return INSTANCE;
}
4. (Optional) Implement Thread‑Safe Initialization
If the application may create the instance from multiple threads, consider one of the following approaches:
- Eager Initialization – Instantiate the object at class loading time. This is inherently thread‑safe because class loading is synchronized by the JVM.
- Lazy Initialization with Synchronized Method – Synchronize the accessor method to ensure only one thread creates the instance.
- Double‑Checked Locking – Combine lazy initialization with a volatile field and a synchronized block to minimize synchronization overhead.
- Enum Singleton – put to work the
enumtype, which guarantees a single instance with built‑in serialization safety.
Below we discuss each approach in detail.
5. (Optional) Add Utility Methods and Fields
Once the singleton structure is in place, you can add any required state variables, business logic, or helper methods.
private String config;
public void setConfig(String config) {
this.config = config;
}
public String getConfig() {
return config;
}
Detailed Initialization Strategies
Eager Initialization
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() { }
public static Singleton getInstance() {
return INSTANCE;
}
}
Why use eager initialization?
- The instance is created when the class is loaded, which happens once during JVM startup.
- No risk of
NullPointerExceptiondue to uninitialized fields. - Naturally thread‑safe without additional synchronization.
Lazy Initialization with Synchronized Method
public class Singleton {
private static Singleton INSTANCE;
private Singleton() { }
public static synchronized Singleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
Pros and cons
- Pros: Guarantees a single instance even in a multithreaded environment.
- Cons: Every call to
getInstance()acquires a lock, which can become a performance bottleneck if the method is invoked frequently.
Double‑Checked Locking (Lazy and Thread‑Safe)
public class Singleton {
private static volatile Singleton INSTANCE; // volatile ensures visibility
private Singleton() { }
public static Singleton getInstance() {
if (INSTANCE == null) { // first check (no lock)
synchronized (Singleton.class) { // acquire lock
if (INSTANCE == null) { // second check (inside lock)
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
Key points
- The volatile keyword prevents the JVM from reordering reads/writes, ensuring that once the instance is assigned, all threads see the fully constructed object.
- The double‑checked pattern minimizes lock contention because the synchronized block is entered only when the instance is actually null.
Enum Singleton
public enum Singleton {
INSTANCE;
// optional initialization
public void doSomething() {
// implementation
}
}
Advantages
- Java’s
enumtype guarantees a single instance and handles serialization, deserialization, and reflection protection automatically. - The enum instance is instantiated when the enum class is loaded, similar to eager initialization, but with less code.
Common Pitfalls and How to Avoid Them
- Forgetting to make the constructor private – Allows external code to create multiple instances, breaking the singleton contract.
- Using non‑static fields for the instance – Each object would have its own copy, defeating the purpose.
- Neglecting thread safety – In a concurrent environment, without proper synchronization, two threads might simultaneously create separate instances.
- Relying on
==for equality – Since there is only one instance,instance1 == instance2should always be true; however, if multiple class loaders are involved (e.g., in OSGi), separate instances can appear. Use a single class loader whenever possible.
Testing the Singleton
To verify that your singleton works correctly, you can write a simple test:
- Create two references using
getInstance(). - Compare their hash codes (
System.identityHashCode). - Ensure both references point to the same object.
Singleton a = Singleton.getInstance();
Singleton b = Singleton.getInstance();
assert a == b; // true
assert System.identityHashCode(a) == System.identityHashCode(b); // true
If the assertions pass, the singleton property holds.
Conclusion
Implementing a singleton class in java involves a few clear steps: declare a private constructor, store a static instance, and expose a thread‑safe accessor. And the choice of initialization strategy—eager, lazy with synchronization, double‑checked locking, or enum—depends on your project’s concurrency requirements and performance considerations. By following the outlined steps and being mindful of common pitfalls, you can confidently create a singleton that centralizes control, improves resource management, and maintains a clean, maintainable codebase Worth keeping that in mind..
Advanced Topics
Serialization and Deserialization
When a singleton implements java.io.Serializable, the default deserialization mechanism can create a new instance, breaking the singleton guarantee. To preserve uniqueness, implement the readResolve method:
private Object readResolve() {
return INSTANCE; // return the canonical instance
}
This ensures that any deserialized object is replaced by the existing singleton instance.
Guarding Against Reflection
Reflection can invoke the private constructor and instantiate additional objects. A defensive approach is to throw an exception if a second instantiation is attempted:
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public enum Singleton {
INSTANCE;
private Singleton() {
if (INSTANCE != null) {
throw new IllegalStateException("Already instantiated");
}
}
public void doSomething() {
// implementation
}
}
With the enum approach, the JVM already prevents reflective instantiation, but if you prefer a class‑based singleton, the holder‑idiom combined with the check above adds safety That alone is useful..
Initialization‑on‑Demand Holder Idiom
This idiom combines lazy initialization with thread‑safety without explicit synchronization:
public class Singleton {
private Singleton() {}
private static class Holder {
static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE;
}
}
The inner class Holder is loaded only when getInstance() is invoked for the first time, guaranteeing that the instance is created lazily and safely.
Preventing Cloning
If your singleton extends a class that implements Cloneable, override clone to throw CloneNotSupportedException:
@Override
protected Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException("Cloning a singleton is not allowed.");
}
When to Avoid Singleton
While singletons are useful for managing shared resources (e.g., configuration managers, logging facilities, connection pools), they can introduce hidden dependencies and make unit testing difficult. Consider alternatives such as dependency injection, where the object’s lifecycle is managed by a container, or use a simple factory that returns a shared instance only when needed.
Conclusion
By understanding the nuances of initialization strategies, guarding against serialization, reflection, and cloning pitfalls, and recognizing scenarios where a singleton may be detrimental, you can apply the pattern judiciously. Choose the implementation that aligns with your performance, thread‑safety, and maintainability goals—whether it’s the enum singleton for its simplicity and built‑in safety, the holder idiom for lazy, thread‑safe creation, or a carefully synchronized lazy initializer. With these practices in place, your singleton will reliably provide a single point of access while keeping your codebase clean, testable, and strong.