TDSoftware
TDSoftware
Innovation Hub
Singleton design pattern in Java: a practical guide

Singleton design pattern in Java: a practical guide

2 min read

1. Core idea of Singleton design pattern in Java

The Singleton pattern is a creational pattern.

Its purpose is simple:

1. Only one instance of a class should exist in the JVM.

2. Everyone should use that same instance through a global access point.

You can think of it as a “global manager” object.

Instead of doing new everywhere, you call something like:

MySingleton instance = MySingleton.getInstance();

Key goals:

  • Prevent other code from calling `new` and creating more instances.
  • Provide a simple, static method to get the same object every time.

2. Basic Java structure of a Singleton

A typical Singleton design pattern in Java has three key parts:

1. Private constructor

  • So nobody can call `new MySingleton()` from outside.

2. Private static instance field

  • Holds the single object.

3. Public static getInstance() method

  • Returns that one object.

Basic skeleton:

public class MySingleton {

    // 1. private static instance
    private static MySingleton instance;

    // 2. private constructor
    private MySingleton() {
        // optional: initialization code
    }

    // 3. public static access method
    public static MySingleton getInstance() {
        if (instance == null) {
            instance = new MySingleton();
        }
        return instance;
    }
}

This is the classic structure most juniors are expected to know.

But this version is not thread-safe (we’ll come back to that).

3. Eager Singleton (simple and thread-safe)

3.1. What is eager initialization?

Eager initialization means you create the instance immediately when the class is loaded.

You don’t wait until someone calls getInstance().

This is the simplest thread-safe singleton in Java because class loading is thread-safe.

3.2. Eager Singleton example

public class AppConfig {

    // instance is created when the class is loaded
    private static final AppConfig INSTANCE = new AppConfig();

    // private constructor to prevent outside instantiation
    private AppConfig() {
        // for example: load config values
        // from a file or environment variables
    }

    public static AppConfig getInstance() {
        return INSTANCE;
    }

    // example methods
    public String getAppName() {
        return "My App";
    }
}

Why this is good:

  • **Thread-safe by default**: class loading guarantees this.
  • Simple to read and understand.
  • No `synchronized` needed.

Main trade-off:

  • The object is created even if nobody ever calls `getInstance()`.
  • Usually not a big deal unless the object is heavy or you have many such singletons.

For most junior-level interview answers, this is a very solid, correct Singleton example.

4. Lazy Singleton and thread-safety issues

4.1. What is lazy initialization?

Lazy initialization means you only create the instance when it’s first needed.

If the program never uses the Singleton, it never pays the cost of creating it.

4.2. Naive lazy Singleton (not thread-safe)

public class LazySingleton {

    private static LazySingleton instance;

    private LazySingleton() {
        // some initialization
    }

    public static LazySingleton getInstance() {
        if (instance == null) {
            instance = new LazySingleton(); // <-- risk here
        }
        return instance;
    }
}

This looks fine in a single-thread program.

The problem appears when multiple threads call getInstance() at the same time.

Scenario:

  • Thread A checks `instance == null` → true.
  • Thread B checks `instance == null` → still true.
  • Both threads create a new `LazySingleton()` almost at the same time.
  • You end up with **two instances**, which breaks the Singleton guarantee.

That’s why this version is not thread-safe.

For interviews, you should be able to say:

The naive lazy Singleton is not thread-safe because multiple threads can enter getInstance() at the same time and create multiple instances.

5. Simple thread-safe lazy Singleton

There are several advanced ways (like double-checked locking or Bill Pugh’s inner class), but as a fresher you should first know the simple synchronized version.

5.1. Using synchronized on getInstance()

public class SynchronizedLazySingleton {

    private static SynchronizedLazySingleton instance;

    private SynchronizedLazySingleton() {
        // initialization code
    }

    public static synchronized SynchronizedLazySingleton getInstance() {
        if (instance == null) {
            instance = new SynchronizedLazySingleton();
        }
        return instance;
    }
}

What synchronized does here:

  • Only one thread can be inside `getInstance()` at a time.
  • If two threads call it, one waits until the other finishes.
  • This ensures only one instance is ever created.

Pros:

  • Easy to understand.
  • Correctly **thread-safe singleton** for most use cases.

Cons:

  • `synchronized` has some performance overhead.
  • In very high-traffic code, this might be a problem.
  • For most beginner projects and interviews, it’s fine.

5.2. Enum-based Singleton (short mention)

A very elegant and safe way to make a Singleton in Java is using an enum:

public enum EnumSingleton {
    INSTANCE;

    public void doSomething() {
        // behavior here
    }
}

Usage:

EnumSingleton singleton = EnumSingleton.INSTANCE;
singleton.doSomething();

Reasons senior developers like this:

  • **Thread-safe**.
  • Handles serialization correctly.
  • Very small and clear.

However, some beginners find it less intuitive, so for interviews, know both:

  • Classic class-based Singleton with `getInstance()`.
  • Enum-based Singleton as a clean alternative.

6. When to use Singleton

Use the Singleton design pattern in Java when:

6.1. You need exactly one shared instance

Examples:

  • Application-wide configuration (`ConfigManager`).
  • A centralized logger (`Logger` in simple projects).
  • A cache or in-memory registry.

In these cases, it makes sense that there’s only one “source of truth”.

6.2. You need a global access point

If many parts of the code need the same object and passing it everywhere is painful, a Singleton can be a quick solution.

For a small application or classroom project, this is often acceptable.

6.3. You want controlled access and lifecycle

Singleton can help you:

  • Ensure only you control when the instance is created.
  • Avoid accidental `new` calls.

For interviews, a good answer is:

I use Singleton when I need one shared instance across the application, such as configuration or a simple logging utility.

7. When you should avoid Singleton

Singleton is easy to overuse.

In real projects, too many Singletons usually become a problem.

7.1. Makes testing harder

If a class uses MySingleton.getInstance() directly:

  • It’s harder to replace that Singleton with a mock in unit tests.
  • You can’t easily inject a different implementation.

Better approach in larger projects:

  • Use **dependency injection** (pass dependencies via constructors), instead of singletons everywhere.

7.2. Hidden global state

Singleton is basically global state with an OOP wrapper.

Problems:

  • Any class can change the Singleton’s internal state.
  • Harder to track who changed what.
  • Bugs become tricky: “somewhere in the app, something changed this shared object”.

7.3. Premature optimization or laziness

Sometimes developers use Singleton just because it’s “easy to access”:

SomeService.getInstance().doStuff();

Instead of thinking about proper design:

  • Who should own this object?
  • Who should create it?
  • Can I pass it as a dependency?

For many cases, a normal class with one shared instance managed by a higher-level object is cleaner than a true Singleton.

7.4. Multi-thread complexity

Correct thread-safe Singletons require:

  • `synchronized`, or
  • proper double-checked locking with `volatile`, or
  • the enum trick.

Beginners often get it wrong and introduce subtle bugs.

If your project is not very small, it’s often better to avoid DIY global singletons.

8. Common Java interview questions about Singleton

Here are some typical interview questions related to the Singleton design pattern in Java, with short model answers.

Q1. What is the Singleton design pattern in Java?

Answer:

Singleton is a creational pattern that ensures a class has only one instance in the JVM and provides a global access point to that instance, usually via a static getInstance() method.

Q2. How do you implement a basic Singleton in Java?

Answer:

Make the constructor private, store the single instance in a private static field, and expose a public static getInstance() method. For example, an eager Singleton:

public class MySingleton {
    private static final MySingleton INSTANCE = new MySingleton();
    private MySingleton() {}
    public static MySingleton getInstance() {
        return INSTANCE;
    }
}

Q3. Why is the lazy Singleton implementation not thread-safe?

Answer:

Because multiple threads can enter getInstance() at the same time when instance is null. Each thread may create a new instance, resulting in more than one object, which breaks the Singleton guarantee.

Q4. How can you make a thread-safe singleton?

Answer:

Simplest way: use synchronized on getInstance():

public static synchronized MySingleton getInstance() {
    if (instance == null) {
        instance = new MySingleton();
    }
    return instance;
}

Or use an eager Singleton or an enum-based Singleton, which are thread-safe by design.

Q5. What are disadvantages of using Singleton?

Answer:

It introduces global state, makes unit testing harder, couples your code to a specific implementation, and can lead to hidden dependencies and harder-to-maintain designs if overused.

9. Practical tips for juniors

A few guidelines for using the Singleton design pattern in Java wisely:

1. Start with eager Singleton for simple cases

  • It’s thread-safe and easy to explain in interviews.
  • Good for lightweight objects like configuration holders.

2. Use lazy + synchronized only when you really need lazy loading

  • And when the performance cost of `synchronized` is acceptable.

3. Prefer enum Singleton in more advanced code

  • Clean, robust, and avoids a lot of edge cases.

4. Don’t make everything a Singleton

  • If you’re thinking “I’ll just make it a Singleton” for many classes, that’s a red flag.
  • Prefer normal objects passed via constructors.

5. Be ready to explain trade-offs in interviews

  • Show you understand not just how to write it, but when it’s appropriate.

If you practice writing 2–3 versions of a Singleton (eager, lazy + synchronized, enum) and can explain their pros and cons, you’ll be in a strong position for Java design patterns questions and for using Singleton correctly in small projects.

Related Posts

No image
design-system