Introduction to Singleton in Java and Design Patterns
When you work with Java, you will come across many situations where you need to make sure that only one object of a class is created throughout your entire application. This is exactly where the concept of a singleton class in Java becomes very useful. Understanding singleton and how it connects to design patterns is the first step toward writing smarter and more efficient Java code.
What is a Singleton Class in Java?
Asingleton class in Java is a class that allows only one instance of itself to be created throughout the entire lifecycle of an application. No matter how many times you try to create a new object of that class, you will always get the same single instance back. This is the core idea behind a singleton class in Java.
Think of it like a country having only one president at a time. No matter how many people ask who the president is, there is always one and only one person holding that position at any given time. A singleton class works exactly the same way. There is one instance of the class that is shared across the entire application, and no second instance can ever be created.
In Java, a singleton class is created by making the constructor private so that no outside code can directly create a new object of the class. The class then provides a static method that returns the one and only instance of the class. If the instance does not exist yet, the static method creates it. If it already exists, it simply returns the existing one.
Difference Between Normal Class and Singleton Class
| Comparison Point | Normal Class | Singleton Class |
| Number of Instances | Multiple instances can be created freely | Only one instance of the class exists throughout the application |
| Constructor Access | Constructor is public and accessible from outside | Constructor is private and cannot be accessed from outside the class |
| Object Creation | New objects are created using the new keyword directly | Object is accessed through a static method that controls instance creation |
| Memory Usage | Each new object takes up its own memory space | Only one instance is created and shared, saving memory |
| Global Access | No built-in global access point for the object | Provides a global point of access to the single instance |
| Control Over Instantiation | No control over how many objects are created | Full control over instantiation of a class ensuring only one object exists |
| Use Case | Used when multiple independent objects are needed | Used when exactly one shared object is needed across the application |
What is Singleton Design Pattern in Java?
A design pattern is a known and reusable solution to an often occurring problem in the context of software development. A design pattern is a predefined solution to solve a recurring problem in software design. Almost all of them have a corresponding design pattern, with Singleton being one of the most popular and widely used among them.
A singleton is a creational design pattern that restricts the instantiation of a class to one single instance, while providing access via a global point. Since it takes charge of the way objects are created, it belongs to creational design pattern. The singleton pattern guarantees that however many times different parts of your application try to instantiate an object of a singleton class, they always receive the same single instance in return.
Very few resources discuss the singleton design pattern and its significance in measurable real projects. Most applications are large and have a lot of components, layers, or modules that need to share some common resources like a database connection, a configuration manager, or a logging system. Without this pattern, individual parts of the application may instantiate these resources themselves which wastes memory and creates inconsistencies that can be difficult to track down. To solve that issue, the singleton design pattern ensures a single shared instance of something is used by every part of the application.
Why Use Singleton Design Pattern in Java?
The singleton design pattern in Java and where it has most used or you can say advantages are as follow:
- ☑️ Controlled Instance Creation: Singleton design pattern guarantees that there is only one instance of the class regardless of how many times different components in the application tries to instantiate it. This provides full control of object creation to developers.
- ☑️ Global Point of Access: With the singleton pattern we have only one global point for access to class instance. The static method enables any part of the application to access the singleton instance, without manually passing the object around.
- ☑️ Memory Efficiency: Since there’s only one instance of the class throughout the application, it also saves memory by not creating multiple unnecessary objects that would otherwise consume extra resources.
- ☑️ Consistency Across the Application: The same singleton instance throughout the application ensures that data and behavior is consistent. This becomes particularly important for shared resources, such as configuration settings or database connections.
- ☑️ Lazy Initialization Support: The singleton design pattern provides lazy initialization, meaning the singleton instance is created only when it’s actually needed; not when starting up the application. This improves startup performance.
- ☑️ Used in Thread Pooling: It is also widely used in thread pooling where a single pool of threads are managed and shared through the application instead of creating new threads on every request.
- ☑️ Easy to Maintain: There is only one instance of a class, therefore any change in the behavior of that class will directly affect every part of your application where it is used.
Real-Time Examples of Singleton Usage
Here are two of the most common and practical real-time examples where the singleton design pattern is actively used in real Java projects:
1. Logger
One of the most well-known real-life examples of singletons is a logger. Logging is an important concept in any Java application, as these messages provide visibility into the detailed event flow of your application (i.e error logs, system information related to what is going on at runtime etc…)
There are things like creating a new logger object every time you want to log, which is highly inefficient and would result in inconsistent outputs in logs. Rather, a single logger instance is created at the beginning and used throughout the application. Singleton Logger: As we discussed in Singleton Pattern, all classes and modules that require logging will share the same instance of logger (or have similar behavior), ensuring that any logs go to the same place in a structured manner.
public class Logger {
private static Logger instance;
private Logger() {
// private constructor to prevent outside instantiation
}
public static Logger getInstance() {
if (instance == null) {
instance = new Logger();
}
return instance;
}
public void log(String message) {
System.out.println("LOG: " + message);
}
}
// Usage
public class Main {
public static void main(String[] args) {
Logger logger = Logger.getInstance();
logger.log("Application started");
logger.log("User logged in");
}
}
**Output:**
LOG: Application started
LOG: User logged in
No matter how many classes call Logger.getInstance(), they all get the same logger object and all logs are handled through that one instance.
2. Database Connection
Another very common real-time use case of singleton design pattern is managing a database connection. It is expensive in terms of time and resources, to create a new database connection every single time any part of the application needs access to the database. This can cause a flood of unnecessary open connections, slowing down or crashing the database.
To overcome this, we may use a singleton pattern that creates one instance of database connectivity only at the beginning and will be reused for the entire application. All application module that needs to talk to the DB uses same singleton DB connection instance.
public class DatabaseConnection {
private static DatabaseConnection instance;
private String connectionUrl;
private DatabaseConnection() {
// Simulating database connection setup
this.connectionUrl = "jdbc:mysql://localhost:3306/mydb";
System.out.println("Database connection created");
}
public static DatabaseConnection getInstance() {
if (instance == null) {
instance = new DatabaseConnection();
}
return instance;
}
public String getConnectionUrl() {
return connectionUrl;
}
}
// Usage
public class Main {
public static void main(String[] args) {
DatabaseConnection connection1 = DatabaseConnection.getInstance();
DatabaseConnection connection2 = DatabaseConnection.getInstance();
System.out.println("Connection URL: " + connection1.getConnectionUrl());
System.out.println("Same instance? " + (connection1 == connection2));
}
}
**Output:**
Database connection created
Connection URL: jdbc:mysql://localhost:3306/mydb
Same instance? true
The output clearly shows that even though getInstance() was called twice, the database connection was only created once and both variables point to the exact same singleton instance.
How to Create Singleton Class in Java?
The singleton class can be implemented in multiple ways in Java, but every way follows the same three basic rules. First, define the singleton class so that its constructor is private and outside code cannot construct an object directly. The second step is to create a private static variable inside the class (top level) to hold the singleton instance. Third, give Exemplar a public static method that returns the singleton instance to anybody who needs it. Let’s Take a look step by step until the increasingly sophisticated approach to how you can create a singleton class in Java:
Basic Singleton Implementation (Eager Initialization)
The simplest way to implement a singleton in Java is through eager initialization. In this approach, the singleton instance is created at the time of class loading, which means the object is ready even before anyone actually asks for it. This is called eager initialization because the instance is created eagerly without waiting for it to be requested.
Here is how a basic singleton class looks with eager initialization:
public class Singleton {
// Instance created at the time of class loading
private static final Singleton instance = new Singleton();
// Private constructor to prevent outside instantiation
private Singleton() {
System.out.println("Singleton instance created");
}
public static Singleton getInstance() {
return instance;
}
public void showMessage() {
System.out.println("Hello from Singleton!");
}
}
// Usage
public class Main {
public static void main(String[] args) {
Singleton obj1 = Singleton.getInstance();
Singleton obj2 = Singleton.getInstance();
obj1.showMessage();
System.out.println("Same instance? " + (obj1 == obj2));
}
}
**Output:**
Singleton instance created
Hello from Singleton!
Same instance? true
How it works:
- The
instancevariable is declared asprivate static finaland is assigned a newSingletonobject directly at the point of declaration - This means the singleton instance is created as soon as the class is loaded by the JVM, even before the
getInstance()method is called for the first time - The constructor is private so no outside class can create a new object using the new keyword
- The
getInstance()method simply returns the already created instance every time it is called - The output confirms that both
obj1andobj2point to the exact same singleton instance
When to use eager initialization: Eager initialization is a good choice when you know that the singleton instance will always be needed during the application’s runtime and the cost of creating it upfront is not a concern. It is simple, straightforward, and naturally thread-safe because the instance is created at class loading time before any thread can access it.
Limitation: The only downside of eager initialization is that the singleton instance is created even if it is never actually used during the application’s runtime. This can waste memory and resources if the singleton object is heavy and the application never ends up needing it.
Lazy Initialization in Singleton Class
Lazy initialization mitigates one big limitation imposed by eager initialization. This time, the singleton instance is not created at the time of class loading. Instead, it is instantiated only when the getInstance() method is invoked for the first time. This means an object is created only when it is actually needed, which is why it is called lazy initialization.
Here is how a singleton class looks with lazy initialization:
public class Singleton {
// Instance is not created yet, just declared
private static Singleton instance;
// Private constructor to prevent outside instantiation
private Singleton() {
System.out.println("Singleton instance created");
}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
public void showMessage() {
System.out.println("Hello from Lazy Singleton!");
}
}
// Usage
public class Main {
public static void main(String[] args) {
System.out.println("Before calling getInstance()");
Singleton obj1 = Singleton.getInstance();
Singleton obj2 = Singleton.getInstance();
obj1.showMessage();
System.out.println("Same instance? " + (obj1 == obj2));
}
}
**Output:**
Before calling getInstance()
Singleton instance created
Hello from Lazy Singleton!
Same instance? true
How it works:
- ☑️ The
instancevariable is declared but not assigned any value at the start, so no object is created when the class is loaded - ☑️ When
getInstance()is called for the first time, it checks ifinstanceis null. Since it is null on the first call, a new singleton object is created and assigned toinstance - ☑️ From the second call onwards,
instanceis no longer null so the existing instance is returned directly without creating a new one - ☑️ The output clearly shows that the singleton instance is only created after
getInstance()is called, not before
Lazy initialization: Lazy initialization can be used when the singleton object is heavy and resource consumption and you want to create it once its actually needed. It is used so as to improve the application startup performance, where, an object does not get created at the start unnecessarily and hence saves that extra time.
Limitation: The basic lazy initialization approach works perfect in a single threaded environment. But it has one grave issue in a multi threaded environment. So if, for instance, two threads happen to call getInstance() at exactly the same time, both threads potentially find that instance is null and end up creating a new singleton object thereby violating the fundamental rule of only having one single instance. This is precisely the issue thread safe singleton implementations are intended to address, which we will discuss in the next section.
Thread Safe Singleton in Java
If you are to implement a singleton class with lazy initialization, everything is fine when a single thread is running. However, no real Java application works under one thread. Write an answer for Most modern applications are multi-threaded, meaning that multiple threads run simultaneously and can access the same resources at the same time. This is where thread safety becomes very critical for your singleton class in Java.
Thread safe singleton: A thread safe singleton, as the name suggests is a singleton implementation, which ensures that even if multiple threads are trying to access the getInstance() method simultaneously there would be only one instance of class created. Without thread safety, your singleton can break silently in a multi-threaded environment which leads to very difficult bugs that are hard to reproduce and fix.
Why Thread Safety is Important in Singleton?
The Multi-Thread Issue
The getInstance() method in a simple lazy initialization singleton checks whether the instance is null and create a new one if so. This is ok when only one thread runs. But what happens when two threads call getInstance() at the same moment? Here is what can go wrong:
So, Thread 1 invokes getInstance() and sees if this instance is null. It is null and ready to instantiate a new object.
Thread 2 also calls getInstance() and checks whether the instance is null before Thread 1 has finished creating the object. It also gets null since Thread 1 did not completed yet.
Thread 1 and Thread 2 each create their own instances of the singleton class. Now the application has two distinct singleton objects: this entire construction breaks the singleton pattern and destroys the whole idea to use it.
Race Condition
And this scenario in which 2 or more threads try to read and manipulate the same resource concurrently is known as race condition. In the scenario of a singleton class, a race condition occurs if multiple threads attempt to create the single instance simultaneously. This decision is nondeterministic and entirely dependent on which thread wins the race to get there first, a situation that can alter every time the application runs. Race Conditions — The Genie of multi-threaded Java applications, one of the deadliest problems in a threaded environment It is flaky, non deterministic but extremely difficult to debug.
Ways to Create Thread Safe Singleton
Here are the two most commonly used ways to create a thread safe singleton in Java:
1. Synchronized Method
The easiest way to make a singleton thread safe is by adding the synchronized keyword in getInstance() method. If a method is synchronized, there can be only one thread that will execute it at the same time. This makes all other threads that attempt to call the method at the same time wait for the thread to finish executing.
javapublic class Singleton {
private static Singleton instance;
private Singleton() {
System.out.println("Singleton instance created");
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
public void showMessage() {
System.out.println("Hello from Thread Safe Singleton!");
}
}
// Usage
public class Main {
public static void main(String[] args) {
// Simulating two threads accessing getInstance() at the same time
Thread thread1 = new Thread(() -> {
Singleton obj = Singleton.getInstance();
obj.showMessage();
});
Thread thread2 = new Thread(() -> {
Singleton obj = Singleton.getInstance();
obj.showMessage();
});
thread1.start();
thread2.start();
}
}
**Output:**
Singleton instance created
Hello from Thread Safe Singleton!
Hello from Thread Safe Singleton!
How it works:
☑️ Since, getInstance() is marked as synchronized so only one thread can enter this method at a time and other will have to wait until the first one exits the method.
☑️ It ensures the method will be forced to execute one by one even in case multiple threads call getInstance() simultaneously. It ensures that the singleton is created only once despite multiple threads attempting to access it simultaneously.
Limitation:
Although method synchronization solves the thread safety problem, it incurs a performance overhead. This means that whenever any thread calls getInstance(), it attempts to grab a lock on this method even when the instance already exists. Especially in cases where getInstance() is called very often, this hindered application performance due to unnecessary synchronization.
2. Double-Checked Locking
Double-checked locking is a smarter and more efficient way to create a thread safe singleton. It solves the performance problem of the synchronized method approach by only synchronizing the critical section of code where the instance is actually created, instead of synchronizing the entire getInstance() method.
It is called double-checked locking because it checks whether the instance is null twice. The first check happens outside the synchronized block to avoid unnecessary locking once the instance is already created. The second check happens inside the synchronized block to make sure only one thread creates the instance even when multiple threads pass the first check at the same time.
javapublic class Singleton {
private static volatile Singleton instance;
private Singleton() {
System.out.println("Singleton instance created");
}
public static Singleton getInstance() {
// First check: no synchronization needed if instance already exists
if (instance == null) {
// Synchronize only when instance is null
synchronized (Singleton.class) {
// Second check: ensure only one thread creates the instance
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
public void showMessage() {
System.out.println("Hello from Double-Checked Locking Singleton!");
}
}
// Usage
public class Main {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
Singleton obj = Singleton.getInstance();
obj.showMessage();
});
Thread thread2 = new Thread(() -> {
Singleton obj = Singleton.getInstance();
obj.showMessage();
});
thread1.start();
thread2.start();
}
}
**Output:**
Singleton instance created
Hello from Double-Checked Locking Singleton!
Hello from Double-Checked Locking Singleton!
How it works:
☑️ The volatile keyword on the instance variable is very important here. It ensures that when one thread creates the singleton instance and writes it to memory, all other threads immediately see the updated value. Without volatile, a thread might read a stale cached value of instance and incorrectly think it is still null.
☑️ The first if (instance == null) check happens without any synchronization. Once the instance is created, all subsequent calls to getInstance() will find the instance is not null and return it directly without ever entering the synchronized block. This makes the method very fast after the first call.
☑️ The synchronized block is only entered when the instance is null, which only happens once during the entire lifetime of the application. Inside the synchronized block, the second if (instance == null) check ensures that even if two threads both passed the first check at the same time, only one of them will actually create the instance. The second thread will find the instance already created when it gets its turn inside the synchronized block.
When to use double-checked locking:
Double-checked locking is the recommended approach for creating a thread safe singleton in Java when performance is important. It combines the safety of synchronization with the efficiency of avoiding unnecessary locking after the instance has been created, making it a practical and reliable choice for real Java projects.
Enum Singleton in Java
When it comes to implementing a singleton class in Java, most developers think of the traditional approaches like eager initialization, lazy initialization, or double-checked locking. However, there is one more approach that is considered the most simple, safe, and reliable of all, which is the enum singleton.
An enum singleton in Java uses Java’s enum type to implement the singleton pattern. In Java, an enum by its very nature guarantees that only one instance of each enum constant is created throughout the entire application. This makes it a perfect fit for implementing a singleton without writing any extra code to handle thread safety or prevent multiple instance creation.
Joshua Bloch, the author of the well-known book Effective Java, recommends using enum as the best way to implement a singleton in Java. The reason is simple. Java itself handles all the heavy lifting for you when you use an enum, from preventing multiple instances to handling thread safety and even protecting against serialization issues.
Here is how an enum singleton looks in Java:
public enum Singleton {
// Single instance of the enum
INSTANCE;
// Method to show message
public void showMessage() {
System.out.println("Hello from Enum Singleton!");
}
public void log(String message) {
System.out.println("LOG: " + message);
}
}
// Usage
public class Main {
public static void main(String[] args) {
// Accessing the singleton instance
Singleton obj1 = Singleton.INSTANCE;
Singleton obj2 = Singleton.INSTANCE;
obj1.showMessage();
obj1.log("Application started");
System.out.println("Same instance? " + (obj1 == obj2));
}
}
**Output:**
Hello from Enum Singleton!
LOG: Application started
Same instance? true
How it works:
- ☑️ The
enumkeyword is used instead ofclassto define the singleton - ☑️
INSTANCEis the single enum constant that represents the one and only instance of the singleton - ☑️ Java guarantees that only one instance of each enum constant is ever created, so there is no need to write any code to control instance creation
- ☑️ The singleton is accessed simply by calling
Singleton.INSTANCEfrom anywhere in the application - ☑️ Both
obj1andobj2point to the exact same instance, which the output confirms
Why Enum is the Best Way to Implement Singleton?
Here are the key advantages of using enum singleton and why it stands out in terms of simplicity and safety compared to all other singleton implementations:
- ☑️ Simplest Implementation: Enum singleton is by far the simplest way to implement a singleton class in Java. You do not need to write a private constructor, a static instance variable, or a getInstance() method. The entire singleton is just a few lines of code, which makes it very easy to read and understand.
- ☑️ Naturally Thread Safe: Java guarantees that enum constants are created only once and are initialized in a thread-safe manner by the JVM during class loading. This means you get thread safety completely for free without writing any synchronized blocks or using the volatile keyword. No matter how many threads try to access Singleton.INSTANCE at the same time, they will always get the same instance without any risk of a race condition.
- ☑️ Protection Against Serialization: One of the biggest problems with traditional singleton implementations is that when you serialize and then deserialize a singleton object, Java can create a brand new instance of the class during deserialization, which breaks the singleton pattern. With enum singleton, Java handles serialization automatically and guarantees that the same instance is returned even after deserialization. You do not need to write any extra code to handle this.
- ☑️ Protection Against Reflection: As we discussed earlier, Java’s reflection API can be used to break traditional singleton implementations by forcefully calling the private constructor to create a new instance. Enum singleton is completely protected against this because Java does not allow the creation of new enum instances through reflection. If someone tries to use reflection to create a new enum instance, Java will throw an exception, keeping your singleton safe.
- ☑️ No Extra Code Needed: With traditional singleton implementations, you have to carefully write and test code to handle thread safety, serialization, and reflection attacks. With enum singleton, all of these concerns are handled by Java itself, which means less code to write, less code to test, and fewer chances of making a mistake.
- ☑️ Reliable and Consistent: Because Java itself manages the lifecycle of enum constants, you can be completely confident that your enum singleton will behave correctly in every situation, whether it is in a single-threaded environment, a multi-threaded environment, or a distributed system where serialization is involved
- ☑️ Recommended by Java Experts: As mentioned earlier, enum singleton is the approach recommended by Joshua Bloch in Effective Java, which is one of the most respected books in the Java community. This recommendation alone is a strong reason to prefer enum singleton over other approaches when simplicity and safety are the top priorities.
Different Ways to Implement Singleton in Java
So far we have covered several ways to implement a singleton class in Java. Each approach has its own strengths and weaknesses, and the right choice depends on the specific needs of your project. Before deciding which one to use, it helps to see all the methods side by side so you can make a clear and informed decision.
Comparison of Singleton Implementation Methods
Here is a detailed comparison of all four singleton implementation methods, eager initialization, lazy initialization, thread safe singleton, and enum singleton:
| Comparison Point | Eager Initialization | Lazy Initialization | Thread Safe Singleton (Double-Checked Locking) | Enum Singleton |
| When Instance is Created | At the time of class loading, before getInstance() is called | Only when getInstance() is called for the first time | Only when getInstance() is called for the first time | At the time of class loading by the JVM |
| Thread Safety | Yes, naturally thread safe because instance is created at class loading time | No, not thread safe in a multi-threaded environment | Yes, thread safe through synchronized block and volatile keyword | Yes, completely thread safe guaranteed by the JVM |
| Performance | Fast, no synchronization overhead | Fast in single-threaded environment but unsafe in multi-threaded | Slightly slower due to synchronization but optimized with double-checked locking | Fastest, no synchronization overhead at all |
| Memory Usage | Instance is created even if never used, which can waste memory | Memory efficient, instance is only created when needed | Memory efficient, instance is only created when needed | Memory efficient, JVM manages instance creation |
| Serialization Safe | No, extra code needed to handle serialization | No, extra code needed to handle serialization | No, extra code needed to handle serialization | Yes, fully serialization safe out of the box |
| Reflection Safe | No, reflection can break the singleton | No, reflection can break the singleton | No, reflection can break the singleton | Yes, fully protected against reflection attacks |
| Code Complexity | Simple and straightforward | Simple but unsafe in multi-threaded environment | Moderate, requires careful use of volatile and synchronized | Simplest of all, just a few lines of code |
| Best Use Case | When singleton is always needed and memory is not a concern | When singleton is rarely used in a single-threaded application | When performance and thread safety both matter in a multi-threaded application | When simplicity, thread safety, and serialization safety are all required |
| Recommended For | Small applications where the singleton will always be used | Single-threaded applications with memory constraints | Large multi-threaded enterprise applications | Most real-world Java projects where safety and simplicity are the priority |
| Risk of Multiple Instances | No risk | High risk in multi-threaded environment | No risk when implemented correctly | No risk, guaranteed by the JVM |
Looking at this comparison, it is clear that each method has its place depending on the situation. For most modern Java projects where thread safety, serialization safety, and simplicity all matter, enum singleton is the strongest and most reliable choice. For large enterprise applications where lazy initialization and fine-grained control over synchronization are needed, double-checked locking is the recommended approach. For simple single-threaded applications, lazy initialization works well. And for cases where the singleton will always be used and simplicity is the top priority, eager initialization gets the job done cleanly.
Advantages and Limitations of Singleton Pattern in Java
Like any design pattern, the singleton pattern in Java comes with its own set of strengths and weaknesses. Here is a clear look at both sides:
Advantages
- ☑️ Single Instance Control: The singleton pattern ensures that only one instance of a class exists throughout the entire application, which gives developers complete control over object creation and prevents unnecessary duplication of resources.
- ☑️ Global Point of Access: The singleton instance can be accessed from anywhere in the application through the static getInstance() method, making it very convenient to share a common resource across different parts of the application without passing the object around manually.
- ☑️ Memory Efficiency: Since only one instance of the singleton class is created and reused throughout the application, it saves memory compared to creating multiple objects of the same class unnecessarily.
- ☑️ Consistent State: Because all parts of the application share the same singleton instance, the data and state of that instance remain consistent throughout, which reduces the chances of conflicts and inconsistencies in the application.
- ☑️ Useful for Shared Resources: The singleton pattern is ideal for managing shared resources like database connections, configuration settings, thread pools, and logging systems where having one centralized instance makes the most sense.
- ☑️ Supports Lazy Initialization: The singleton pattern supports lazy initialization, which means the instance can be created only when it is actually needed, improving the startup performance of the application.
Limitations
- ☑️ Difficult to Unit Test: Since the singleton maintains a global state throughout the application, it can make unit testing very challenging. Tests that depend on a singleton can interfere with each other because they all share the same instance, making it hard to isolate and test individual components independently.
- ☑️ Tight Coupling: Using a singleton across many parts of the application can create tight coupling between the singleton class and the classes that depend on it. This makes the code harder to maintain and less flexible to change over time.
- ☑️ Hidden Dependencies: When classes use a singleton directly through its static method, the dependency is not always visible from the outside. This makes the code harder to understand and maintain compared to dependencies that are passed through constructors or methods.
- ☑️ Concurrency Challenges: Implementing a thread safe singleton requires careful handling using synchronized blocks, volatile keywords, or enum. If not done correctly, a singleton can break in a multi-threaded environment and create multiple instances, defeating its entire purpose.
- ☑️ Can Become an Anti-Pattern: When the singleton pattern is overused or used in the wrong situations, it can make the codebase difficult to scale, test, and maintain. This is why the singleton pattern should be used judiciously and only when a single shared instance genuinely makes sense.
- ☑️ Serialization Risks: Traditional singleton implementations are vulnerable to serialization. When a singleton object is serialized and deserialized, a new instance can be created, which breaks the singleton contract unless extra care is taken to handle this case explicitly.
When to Use Singleton Class in Java?
Knowing when to use the singleton pattern is just as important as knowing how to implement it. Using it in the right situations makes your application cleaner and more efficient. Using it in the wrong situations can create problems that are hard to fix later. Here is a clear look at when you should and should not use the singleton class in Java:
| Scenario | When to Use Singleton | When NOT to Use Singleton |
| Database Connection | Use singleton when your application needs one shared database connection that all parts of the application can reuse efficiently | Do not use singleton if your application needs multiple separate database connections for different databases or different user sessions |
| Logger | Use singleton for a centralized logging system where all parts of the application write logs through one shared logger instance | Do not use singleton if different modules need completely separate and independent logging configurations |
| Configuration Manager | Use singleton to hold application-wide configuration settings that are loaded once at startup and shared across the entire application | Do not use singleton if different parts of the application need different sets of configuration values that change independently |
| Thread Pooling | Use singleton to manage a single shared thread pool that handles all background tasks across the application | Do not use singleton if different parts of the application need separate thread pools with different settings and priorities |
| Cache Management | Use singleton for a centralized cache that stores and serves frequently accessed data to all parts of the application | Do not use singleton if different users or sessions need their own separate cache that should not be shared with others |
| Unit Testing | Singleton works in integration testing where the full application context is needed and shared state is acceptable | Do not use singleton in classes that need to be unit tested independently as the shared state makes tests interfere with each other |
| Object with Frequent Creation | Use singleton when creating the object is expensive in terms of time and resources and the same instance can be safely reused | Do not use singleton if the object holds state that is different for each user, request, or session in the application |
| Shared Hardware Resource | Use singleton to manage access to a shared hardware resource like a printer or a file system where only one point of control is needed | Do not use singleton if multiple independent instances of the resource handler are needed for parallel processing |
| Small Single-Threaded App | Singleton works well in small single-threaded applications where simplicity is the priority and thread safety is not a concern | Do not use singleton in large multi-threaded applications without proper thread safe implementation as it can lead to race conditions |
| Global State Management | Use singleton when you genuinely need one centralized place to manage a piece of global state that the entire application depends on | Do not use singleton just because it is convenient. Overusing singleton for global state management leads to tight coupling and makes the code hard to maintain and scale |
Conclusion: Understanding Singleton Design Pattern in Java
Here is a quick summary of everything we covered in this blog:
Singleton
- ☑️ A singleton class in Java ensures that only one instance of the class exists throughout the entire application
- ☑️ It provides a global point of access to that single instance through a static method
- ☑️ The constructor is kept private to prevent outside code from creating new objects directly
- ☑️ It is one of the most widely used and practically important concepts in Java development
Design Pattern
- ☑️ The singleton design pattern is a creational design pattern that controls how objects are created
- ☑️ It solves the real-world problem of managing shared resources like database connections, loggers, configuration managers, and thread pools
- ☑️ Like any design pattern, singleton should be used judiciously and only in situations where a single shared instance genuinely makes sense
- ☑️ Overusing it can lead to tight coupling, hidden dependencies, and code that is hard to test and maintain
Implementation
- ☑️ There are multiple ways to implement a singleton class in Java including eager initialization, lazy initialization, synchronized method, double-checked locking, and enum singleton
- ☑️ Eager initialization is simple but creates the instance even if it is never used
- ☑️ Lazy initialization is memory efficient but not thread safe on its own
- ☑️ Double-checked locking provides both thread safety and performance in multi-threaded applications
- ☑️ Enum singleton is the simplest, safest, and most reliable approach as it handles thread safety, serialization, and reflection protection automatically
- ☑️ Choosing the right implementation depends on the specific needs of your project
If you want to truly master Java concepts like the singleton design pattern and build skills that work in real projects, Payilagam is the right place to start. Recognized as the Best Software Training Institute in Chennai, Payilagam offers hands-on, project-based training that goes beyond textbooks and prepares you for real-world Java development. With experienced trainers and an industry-focused curriculum, Payilagam, the Best Java Training Institute in Chennai, gives you the confidence and skills to crack interviews and build production-ready Java applications from day one. If you are serious about your Java career, Payilagam is where your journey begins.
FAQs about Singleton Class in Java
What is a singleton class in Java?
A singleton class in Java is a class that allows only one instance of itself to be created throughout the entire lifecycle of the application. No matter how many times different parts of the application try to create a new object of that class, they always get the same single instance back. This is achieved by making the constructor private, keeping a static reference to the single instance inside the class, and providing a public static method that returns that instance.
How to create singleton class in Java?
To create a singleton class in Java you need to follow these steps. First, declare the constructor as private so no outside code can create a new object directly. Second, create a private static variable inside the class to hold the single instance. Third, provide a public static getInstance() method that checks if the instance already exists and creates it only if it does not. Depending on your needs you can implement it using eager initialization, lazy initialization, double-checked locking, or enum singleton which is the simplest and safest approach.
Why singleton design pattern is used?
The singleton design pattern is used when exactly one shared instance of a class is needed across the entire application. It is commonly used for managing shared resources like database connections, logging systems, configuration managers, and thread pools where creating multiple instances would waste memory, cause inconsistency, or lead to conflicts. The singleton pattern gives developers full control over object creation and provides a global point of access to the single instance from anywhere in the application.
What is thread safe singleton?
A thread safe singleton is a singleton implementation that guarantees only one instance of the class is created even when multiple threads try to access the getInstance() method at the same time. Without thread safety, two threads can both find the instance to be null at the same time and end up creating two separate instances, which breaks the singleton pattern. Thread safety can be achieved using a synchronized method, double-checked locking with the volatile keyword, or by using an enum singleton which is naturally thread safe without any extra code.
What is enum singleton in Java?
An enum singleton in Java is a way of implementing the singleton pattern using Java’s enum type instead of a regular class. Since Java guarantees that each enum constant is created only once by the JVM, using an enum automatically gives you a singleton without writing any extra code for thread safety or instance control. Enum singleton is also naturally protected against serialization issues and reflection attacks, which makes it the simplest, safest, and most reliable way to implement a singleton class in Java. It is the approach recommended by Joshua Bloch in his book Effective Java.
When should we use singleton in Java?
Singleton should be used when your application genuinely needs one shared instance of a class that is accessed by multiple parts of the application. Common situations include managing a single database connection, a centralized logging system, application-wide configuration settings, a shared cache, or a thread pool. However singleton should not be used just because it is convenient. It should be avoided in situations where different users or sessions need separate instances, where the class needs to be independently unit tested, or where using a single shared instance would create tight coupling and make the code harder to maintain and scale.

