Java
Singleton Pattern
Programming
Software Development
Code Efficiency

What is an efficient way to implement a singleton pattern in Java?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

A correct Java singleton has to do more than expose one global object. It must create the instance exactly once, publish it safely across threads, and avoid unnecessary synchronization overhead. In practice, the best implementations are usually the initialization-on-demand holder idiom or an enum singleton.

What a Singleton Must Guarantee

A singleton implementation has three jobs:

  • prevent outside construction
  • ensure only one instance is created
  • make that instance safely visible to all threads

The naive lazy implementation looks harmless but is not thread-safe:

java
1public class BadSingleton {
2    private static BadSingleton instance;
3
4    private BadSingleton() {
5    }
6
7    public static BadSingleton getInstance() {
8        if (instance == null) {
9            instance = new BadSingleton();
10        }
11        return instance;
12    }
13}

Two threads can enter getInstance at the same time and create two different objects.

The Holder Idiom Is Usually the Best Default

The initialization-on-demand holder idiom gives you lazy initialization without synchronizing every access.

java
1public class AppConfig {
2    private AppConfig() {
3    }
4
5    private static class Holder {
6        private static final AppConfig INSTANCE = new AppConfig();
7    }
8
9    public static AppConfig getInstance() {
10        return Holder.INSTANCE;
11    }
12}

This works because class initialization in the JVM is thread-safe. The nested holder class is not initialized until getInstance is called for the first time.

That gives you:

  • lazy creation
  • no locking on repeated reads
  • simple, readable code

For most ordinary singleton cases, that is the right balance.

Enum Singleton for Maximum Robustness

If you do not specifically need the holder-class style, an enum singleton is even harder to break.

java
1public enum MetricsRegistry {
2    INSTANCE;
3
4    public void record(String eventName) {
5        System.out.println("Recorded " + eventName);
6    }
7}

This approach is concise and has two strong advantages:

  • serialization works correctly by default
  • reflection-based attacks are harder to use to create extra instances

The main drawback is that it feels less like a conventional class to some teams, but technically it is a strong choice.

Double-Checked Locking Works, but It Is Not the First Choice

Double-checked locking became valid in modern Java once volatile semantics were fixed, but it is still more complex than the alternatives.

java
1public class LazyCache {
2    private static volatile LazyCache instance;
3
4    private LazyCache() {
5    }
6
7    public static LazyCache getInstance() {
8        if (instance == null) {
9            synchronized (LazyCache.class) {
10                if (instance == null) {
11                    instance = new LazyCache();
12                }
13            }
14        }
15        return instance;
16    }
17}

This is correct if written carefully, but it is easier to get wrong and harder to read than the holder idiom. Use it only when you have a concrete reason to prefer that style.

Ask Whether You Need a Singleton at All

In many modern Java applications, especially those using Spring or another dependency-injection container, the framework can manage a singleton lifecycle for you. If a service is already container-managed, hand-writing a singleton often adds global state and makes testing harder.

So the real design question is often not "How do I implement a singleton?" but "Should this lifecycle be owned by the application container instead?"

Serialization and Testing Concerns

Class-based singletons can break if they are serialized and deserialized without a readResolve method. They can also make unit testing harder because global state leaks across tests.

That is another reason enum singletons and framework-managed objects are attractive. They reduce the number of edge cases you have to defend manually.

Common Pitfalls

The most common mistake is using the naive lazy singleton in concurrent code.

Another mistake is reaching for double-checked locking when the holder idiom would be simpler and just as efficient for the real use case.

A third issue is using a singleton where dependency injection or ordinary object composition would make the design easier to test and evolve.

Summary

  • A Java singleton must be thread-safe, not just globally accessible
  • The holder idiom is usually the best default for lazy singleton creation
  • Enum singletons are concise and robust, especially around serialization
  • Double-checked locking works but is usually more complexity than you need
  • Before implementing a singleton, ask whether the lifecycle should be managed by a DI container instead

Course illustration
Course illustration

All Rights Reserved.