Singleton Design Pattern in Java: The Most Famous — and Most Frequently Misused — Creational Pattern

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:

  1. Only one instance exists
  2. 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

ApproachThread SafeLazyRecommended
Basic SingletonYesNoSimple
Lazy SingletonNoYesRisky
synchronized SingletonYesYesModerate
Double-Checked LockingYesYesAdvanced
Enum SingletonYesNoPreferred

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:

Factory Method Pattern — Creating Objects Without Hardcoding new Everywhere

Leave a Reply

Your email address will not be published. Required fields are marked *