← The Observatory

Understanding the Singleton Pattern: From Core Concept to Thread-Safe Java

Learn how the Singleton pattern guarantees a single unique instance and global access point, how to implement it safely in multithreaded Java, and the trade-offs to keep in mind.

  • #design patterns
  • #singleton
  • #java
  • #software design
  • #programming
On this page

A country has only one official government. As the design patterns site Refactoring.Guru points out, the government is an excellent example of the Singleton pattern. [1]

Software has the same need in a few places. Some components in your program need to be initialized exactly once, exist as a single unique instance, and be shared across your entire application. This post explains the Singleton pattern from zero: what a design pattern is, what Singleton does, how to implement it in Java, when it breaks, and when you should think twice before using it.

What is a design pattern?

A design pattern is simply a typical solution to a common problem in software architecture. Think of it as a pre-made blueprint that you can customize to fit the specific needs of your codebase. [2]

For this post, that definition is enough, as diving deeper into design patterns would require a separate article. The Singleton pattern specifically was formally defined in the classic book Design Patterns by Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides, known as the Gang of Four (GoF). [3]

What is the Singleton pattern?

The Gang of Four defines its intent simply: ensure a class has only one instance, and provide a global point of access to it. [1][3] It falls under the category of creational design patterns. [1]

Notice that this sentence actually makes two guarantees. The first is that there is only one instance. The second is that anyone can reach it from anywhere (global access). [1] A plain global variable also satisfies the second guarantee, but it does not stop someone from creating another instance of that class. [1] Unlike a global variable, the Singleton pattern gives you both. It is designed to ensure that there is just one instance of the class. [1]

Simple UML class diagram of the Singleton pattern
Simple UML class diagram of the Singleton pattern

What are the characteristics of a Singleton?

Singletons are typically built from three core pieces. [1][3]

A private constructor. The class hides its constructor so that external code cannot create new instances with new. [1][3] This is what stops ordinary code from creating a second instance (later sections show where this isn't airtight).

A padlock on a door
A padlock on a door. Credit: Quim Gil / Wikimedia Commons, CC0

A static access method. The class offers one public method, such as Instance() or getInstance(), that acts as the global access point. It returns the cached object on subsequent calls. [1][3]

One cached instance. The object is kept in a private static field. In the lazy version (lazy initialization), it is created only when somebody asks for it the first time, and every subsequent call returns the object cached in memory. [1][3]

To picture this clearly, imagine an office where the door to the workspace is locked (private constructor): you cannot just walk in and spin up a new staff member on your own—you must go through a single reception desk (the getInstance() access method), which always connects you to the exact same person in charge (instance).

A reception desk
A reception desk. Credit: ITCENTRELJN / Wikimedia Commons, CC0

Put as a recipe, building a Singleton takes three steps. [1] First, make the default constructor private. Second, create a static method that acts as a replacement for the constructor. Third, make every subsequent call to that method return the cached object. [1] That is the core of the pattern; the rest of this post is about performing those three steps safely, as well as the price you pay when using them.

Let's see a real scenario with a simple Singleton in Java

To understand this pattern more deeply, let's look at it in a common scenario. Imagine a small app with a settings screen. The user picks a "dark" theme, and ten different screens across the app need to apply that theme choice. If each screen instantiated its own settings object, one screen might get the value "dark" while another still holds "light". What you want is one single settings object that the entire application relies on.

Source code on a computer monitor

Below is a first version of the Singleton for settings:

public class AppConfig {
    private static AppConfig instance;        // the one and only instance
    private String theme = "dark";

    private AppConfig() { }                   // nobody else can call new

    public static AppConfig getInstance() {   // the global access point
        if (instance == null) {
            instance = new AppConfig();       // created on first request
        }
        return instance;
    }

    public String getTheme() { return theme; }
}

And this is how any screen uses it:

AppConfig a = AppConfig.getInstance();
AppConfig b = AppConfig.getInstance();
System.out.println(a == b);   // true: both variables point to the same object

Read the code above from top to bottom: the private field holds a single instance; the private constructor locks the door to the workspace; and getInstance() acts as the reception desk—on the first call, it sees that the instance field is still null so it creates a new object, and on every subsequent call, it returns the cached object. I compiled and ran this code, and a == b prints true.

Here is what happens, step by step, when the app runs:

  1. The first screen calls getInstance(). The instance field is still null, so the method instantiates AppConfig and stores it.

  2. The second screen calls getInstance(). The instance field is no longer null, so the method skips initialization and returns the stored object.

  3. Every other screen does the same. No other place in the code can call new AppConfig(), so nobody can accidentally create a second settings object.

Why the simple Singleton version breaks and how to fix it

The version above is written using lazy initialization—meaning it creates the object only when someone calls getInstance() for the first time. However, this simple implementation has a hidden problem: it is only safe when the program runs just one task at any given time. [4][5] In a program with multiple threads (several tasks running concurrently), two threads can call the creation method at the same moment and each create a separate instance. [4][5] Then you no longer have a Singleton.

Let's break down this scenario step by step with two threads, A and B. Thread A calls getInstance(), sees that the instance field is null, and starts creating a new object. But before Thread A finishes creating and assigning it, Thread B also calls getInstance() and sees that instance is still null. As a result, both instantiate a new object on their own, leaving each thread holding a different instance. This is just like two employees checking a cabinet, seeing there is no key yet, and each going off to have a new one made: individually, each person acted sensibly, but together they broke the "only one unique key" rule.

A single-lane bridge
A single-lane bridge at Long Load, England. Credit: User:Celiakozlowski / Wikimedia Commons, CC BY 3.0

People have found several ways to fix this problem, and below I will cover 5 common approaches. Each has its own strengths, but also trades something for something else.

1. Eager initialization. Initialize the instance right when the class is loaded into memory by the JVM. [5] As general Java knowledge, because class initialization is sequential, this avoids the race condition above; however, the drawback is waste: the object is still created even if the program never uses it. [5]

2. Synchronizing the getInstance() method (synchronized). Add the synchronized keyword to the access method so that only one thread is allowed to execute this method at a time. This is safe but slow, because threads cannot access it simultaneously and must wait for each other. [5]

3. Double-checked locking. Synchronize only on the first initialization, when the instance does not yet exist. [4] Because only the first creation is synchronized, subsequent calls do not have to wait. So that double-checked locking works properly, the instance field is declared with the volatile keyword. [4]

To explain volatile briefly in my own words (simplified): it makes writes to the instance variable visible to other threads and prevents the JVM from reordering the initialization steps inside new, so other threads see a fully initialized object.

4. Holder idiom (Bill Pugh). Place the instance variable (INSTANCE) inside an internal static class (Holder)—this class is not initialized until getInstance() is called. [5][6] The Java Language Specification guarantees that the class initialization phase is sequential, giving you safe lazy initialization with good performance. [6] One of the resources I referenced also calls this the most widely used approach. [5] Below is an example for a Logger class:

public class Logger {
    private Logger() { }

    private static class Holder {
        private static final Logger INSTANCE = new Logger();
    }

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

    public void log(String message) { System.out.println("LOG: " + message); }
}

As you can see, the code requires no if, no synchronized, and no volatile. I tested this code with 50 threads requesting the Logger at once (a quick test run, not a formal proof), and they all received the exact same Logger instance.

5. Using an enum. Declare the Singleton as a Java enum with a single element:

public enum Settings {
    INSTANCE;

    private int volume = 5;
    public int getVolume() { return volume; }
}
// Usage: int vol = Settings.INSTANCE.getVolume();

As general Java knowledge, Java initializes enum constants in a thread-safe manner; moreover, a summary of Effective Java by Joshua Bloch calls this the preferred approach: an enum provides serialization support out of the box and guards against creating a second instance, even via reflection. [7] Since I haven't read the original book firsthand, I'm sharing this as a strong recommendation worth considering rather than my own conclusion. Furthermore, one drawback mentioned in the articles I referenced is that a Java enum cannot extend another class. [8][9]

Summary of the 5 fixes

  • Eager initialization: created right at class loading, which can be wasteful if never used. [5]

  • synchronized method: safe but slow, because threads must wait for each other. [5]

  • Double-checked locking: synchronizes only on first initialization, requires volatile. [4]

  • Holder idiom: lazy initialization, thread-safe without writing lock statements in your code. [5][6]

  • Enum: preferred in the Effective Java summary; guards against both reflection and serialization, but cannot extend another class. [7][8][9]

Two ways to break a Singleton's uniqueness

Even when a Singleton is written properly, there are still ways to break its uniqueness:

  • Reflection: This Java feature allows code to bypass access visibility rules. It can call your private constructor directly and force it to create a second instance. [7][8][9]

  • Serialization: When you convert an object into a byte stream and reconstruct it later, Java can produce a new object. The fix is to add a readResolve() method. [7][8][9]

The enum version guards against both of these issues, and the Effective Java summary above calls it the preferred approach. [7]

Examples in the standard Java library

You don't have to write code from scratch to encounter Singletons in practice. Even the standard Java library applies this design pattern in familiar places such as java.lang.Runtime.getRuntime() and java.awt.Desktop.getDesktop(). [4][8]

What are the disadvantages?

Despite its convenience, the Singleton pattern comes with real trade-offs that you should weigh carefully. [1][10]

  • It violates the Single Responsibility Principle. The class is forced to shoulder two jobs at once: handling its actual business logic, plus controlling its own lifecycle and uniqueness. [1]

  • It creates tight coupling. Because many classes end up depending on one global instance, they become tightly coupled to it. [10]

  • It can mask bad design. [1] In my view, when developers can grab an object from anywhere, they easily skip thinking about how data should properly flow through the application.

  • It can make unit testing difficult. Because a Singleton carries shared global state, unit testing client code that depends on it can be difficult. [1][10]

  • It introduces multithreading headaches. As we saw with the lazy initialization approach, threads must be handled with care, and creating a thread-safe Singleton in a multithreaded environment takes extra effort. [1][10]

In my view, apart from the Single Responsibility issue, the root of these troubles lies in shared global access. The very feature that makes the Singleton pattern so tempting is the same one that makes your codebase harder to test, decouple, and refactor.

When should you use it and when should you not?

Use it when a class in your program should have just a single instance available to all clients, or when you need stricter control over global variables. [1] The typical use cases mentioned in related resources are shared resources, a logger, configuration, a cache, a thread pool, and database connections. [8][9][10] Simply put:

  • Logger: every component in the application writes to the exact same log file.

  • Configuration: every part of the program reads from one shared set of settings, just like the theme example above.

  • Cache: one shared temporary store so the app doesn't have to reload or re-query the same data twice.

  • Thread pool or database connections: a shared set of threads or database connections that the application draws from, instead of each part creating its own.

In my view, in cases like these, accidentally creating a second instance can waste resources or lead to inconsistent state.

Think twice when, in my view, the "only one" requirement comes purely from convenience rather than a strict need. If you are making a Singleton just because passing variables around between methods feels tedious, you are trading long-term maintainability for short-term convenience.

To be honest, I also looked into what solutions developers typically use instead, but the resources I referenced for this post do not cover those alternatives. So I won't jump to conclusions about them here; that will be a great topic for another post.

Conclusion

The Singleton pattern is built on a small idea: one class, one unique instance, one unique way in. Its three core components are a private constructor, a static access method, and one cached instance. In Java, the holder idiom (called the most widely used approach in [5]) and enum (called the preferred approach in the Effective Java summary [7]) are two notable ways to implement it, whereas the simple lazy initialization version is only safe when a single task runs at a time. [5][6][7]

However, this tool also comes with a price to pay. It is convenient because it is global, and it causes trouble for the exact same reason. Below is a short summary to keep in mind:

  • What it is: a class that has exactly one unique instance, accompanied by a global access point. [1][3]

  • Core structure: a private constructor, a static access method, and one cached instance. [1][3]

  • How to implement in Java: options include eager initialization, a synchronized method, double-checked locking, the holder idiom, or an enum (called the preferred approach in the Effective Java summary), while the simple lazy version is only safe in a single-threaded environment. [4][5][6][7]

  • Key caveats: global state, tight coupling, and difficulty writing unit tests. [1][10]

So the next time you reach for a Singleton, ask yourself one question: does this component truly have to exist as a single unique instance, or am I using it just because it's handy? If it truly must, use it with care. If it is just out of convenience, think twice; alternative solutions will be the topic for another post.

References

  1. Refactoring.Guru. "Singleton". https://refactoring.guru/design-patterns/singleton

  2. Refactoring.Guru. "What is a design pattern?". https://refactoring.guru/design-patterns/what-is-pattern

  3. Townsend, M. "Exploring the Singleton Design Pattern". Microsoft patterns & practices (archived), Feb 2002. https://learn.microsoft.com/en-us/previous-versions/msp-n-p/ee817670%28v=pandp.10%29

  4. Refactoring.Guru. "Singleton in Java (example)". https://refactoring.guru/design-patterns/singleton/java/example

  5. GeeksforGeeks. "Java Singleton Design Pattern Practices with Examples". https://www.geeksforgeeks.org/java/java-singleton-design-pattern-practices-examples/

  6. Wikipedia. "Initialization-on-demand holder idiom". https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom

  7. Matilla, H. "effective-java-summary" (a summary of Effective Java, 2nd edition, by Joshua Bloch; GitHub fork under willmold). https://github.com/willmold/effective-java-summary

  8. TopDev. "Giới thiệu Singleton Pattern". Sep 8, 2018. https://topdev.vn/blog/gioi-thieu-singleton-pattern/

  9. GP Coder. "Hướng dẫn Java Design Pattern - Singleton". https://gpcoder.com/4190-huong-dan-java-design-pattern-singleton/

  10. GeeksforGeeks. "Singleton Design Pattern". Updated Sep 17, 2026. https://www.geeksforgeeks.org/system-design/singleton-design-pattern/

Get new signals by e-mail

A short note when something new is published. No noise, and you can leave with one click.

Which signals?

We send a confirmation first. Nothing else is sent until you confirm.

Discussion

Loading…

More signals

See all signals