How To Make Class Singleton In Java

12 min read

Introduction

Learning how to make class singleton in Java is a fundamental skill for any developer who wants to control object creation, ensure a single point of access, and manage shared resources efficiently. A singleton class restricts instantiation to a single object, which is especially useful for things like configuration managers, logging services, and database connections. This guide walks you through the classic approaches, highlights best practices, and explains the underlying principles so you can confidently implement a strong singleton in your Java projects.

Steps to Create a Singleton Class

1. Declare a Private Static Final Field for the Instance

The first step is to create a private static final field of the class type. Making it final guarantees that the reference cannot be changed after initialization, reinforcing the singleton guarantee.

public class Logger {
    private static final Logger INSTANCE = new Logger();
    // rest of the code
}

2. Make the Constructor Private

A private constructor prevents any other class from invoking new Logger(). This is the core mechanism that enforces the singleton property Simple as that..

private Logger() {
    // optional initialization code
}

3. Provide a Public Static Access Method

Expose a public static method (commonly named getInstance()) that returns the single instance. This method acts as the global access point Most people skip this — try not to..

public static Logger getInstance() {
    return INSTANCE;
}

4. (Optional) Implement Lazy Initialization

If you prefer to defer object creation until it is actually needed, use a static inner holder class or double‑checked locking. The holder‑class approach is both simple and thread‑safe without extra synchronization overhead.

public class Settings {
    private Settings() {}

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

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

5. Ensure Thread Safety

In a multi‑threaded environment, the initialization logic must be thread‑safe. The holder‑class method shown above is inherently safe because the JVM guarantees that static initialization is performed under the class loader’s lock. For eager singletons, simply creating the instance at field declaration time also provides thread safety.

6. Document the Singleton Contract

Add JavaDoc or comments to clarify that the class is a singleton, which helps other developers understand the intended usage and prevents accidental misuse.

/**
 * A thread‑safe singleton that manages application logging.
 * Only one {@code Logger} instance can exist throughout the JVM lifecycle.
 */

7. Test the Implementation

Write unit tests that verify:

  • Multiple calls to getInstance() return the same object (assertSame).
  • The constructor cannot be invoked from outside (assertThrows).
  • The class works correctly under concurrent access (using CountDownLatch or ExecutorService).

Scientific Explanation

Singleton Pattern Overview

The singleton pattern is a creational design pattern that ensures a class has only one instance and provides a global point of access to that instance. It is one of the Gang of Four patterns and is widely used in enterprise Java development Easy to understand, harder to ignore..

Eager vs. Lazy Initialization

  • Eager initialization: The instance is created at class loading time. This approach is simple and guarantees thread safety, but it may waste resources if the singleton is never used.
  • Lazy initialization: The instance is created only when getInstance() is first called. This conserves resources but requires careful synchronization to avoid race conditions.

Thread‑Safety Mechanisms

  1. Synchronization: Wrapping getInstance() in a synchronized block guarantees mutual exclusion but can impact performance.
  2. Double‑checked locking: Reduces synchronization overhead by checking the instance reference twice. Even so, it requires the volatile keyword to prevent instruction reordering.
  3. Static inner class: As demonstrated, the JVM’s class‑loader guarantees that the inner class is initialized only once, providing a clean, thread‑safe lazy solution without explicit locks.

Common Pitfalls

  • Reflection attacks: An attacker can bypass a private constructor using reflection, creating multiple instances. Mitigation includes checking if an instance already exists inside the constructor and throwing an exception.
  • Serialization: Default Java serialization creates a new instance unless readResolve() is implemented to return the singleton instance.
  • Cloning: Implementing Cloneable can lead to duplicate instances unless clone() is overridden to throw an exception or return the singleton.

FAQ

What is the main difference between eager and lazy singletons?

Eager singletons are initialized when the class is loaded, guaranteeing immediate availability but potentially wasting memory. Lazy singletons create the instance only on first access, saving resources but requiring careful thread‑safety handling Turns out it matters..

Can a singleton be made thread‑safe without synchronization?

Yes. Using a static inner holder class (also known as the “initialization‑on‑demand holder” pattern) leverages the JVM’s class‑loader locks, providing thread‑safe lazy initialization without explicit synchronized blocks.

How do I prevent reflection from creating another instance?

Add a check inside the private constructor: if an instance already exists, throw an exception. Example:

private MySingleton() {
    if (instance != null) {
        throw new IllegalStateException("Singleton already instantiated");
    }
}

Is it possible to serialize a singleton?

Yes, but you must implement readObject() (or readResolve()) to return the existing instance instead of creating a new one during deserialization.

When should I use a singleton?

Use a singleton when you need a single, globally accessible resource such as configuration managers, logging frameworks, database connection pools, or caching services. Avoid overusing it; sometimes dependency injection or a simple static utility class may be more appropriate The details matter here. And it works..

Conclusion

Creating a class singleton in Java is straightforward once you understand the core concepts: a private constructor, a static final instance field, and a public static access method. By choosing between eager and lazy initialization and applying appropriate thread‑safety techniques—such as the static inner holder pattern—you can build a reliable singleton that works reliably in both single‑threaded and concurrent environments. Remember to guard against reflection, serialization, and cloning attacks, and always document the singleton contract for future maintainers. With these practices in place, you’ll have a clean, reusable component that adheres to the singleton pattern’s intent and enhances the overall design of your Java applications Worth knowing..

Beyond the basic implementation details, a solid singleton design often benefits from considering how it interacts with testing frameworks, dependency‑injection containers, and evolving architectural trends. Below are several practical extensions that can help you integrate singletons cleanly into larger codebases while preserving their core guarantees It's one of those things that adds up..

1. Unit‑Testing Strategies

Singletons introduce global state, which can make tests interdependent if not handled carefully. Two common approaches mitigate this:

  • Reset‑able Singleton – expose a package‑private (or test‑only) method that clears the internal instance, allowing each test to start with a fresh state.
    // visible only to tests via same package or @VisibleForTesting
    static void reset() {
        instance = null; // forces lazy re‑creation on next getInstance()
    }
    
  • Dependency Injection Wrapper – instead of referencing the singleton directly, inject an interface that the singleton implements. In production you bind the interface to the singleton instance; in tests you supply a mock or stub. This preserves the singleton’s runtime behavior while keeping test code isolated.

2. Enum‑Based Singleton

Joshua Bloch’s Effective Java recommends using an enum to achieve a serialization‑safe, reflection‑ resistant singleton with virtually no boilerplate:

public enum ConfigManager {
    INSTANCE;

    private final Properties props = new Properties();

    ConfigManager() {
        try (InputStream in = getClass().getResourceAsStream("/config.properties")) {
            props.

    public String get(String key) {
        return props.getProperty(key);
    }
}

The JVM guarantees that enum values are instantiated exactly once, even in the presence of serialization or reflective instantiation attempts.

3. Integration with DI Containers

Modern frameworks (Spring, Guice, Dagger) treat a singleton as a scope rather than a hard‑coded class pattern. By annotating a class with @Singleton (Spring) or @Singleton (Guice) and letting the container manage its lifecycle, you gain:

  • Automatic thread‑safe creation and destruction.
  • Ability to swap implementations via configuration or profiles without touching the class code.
  • Clear separation between the singleton contract (the interface) and its concrete implementation.

If you still prefer a hand‑rolled singleton, you can expose it as a bean via a @Configuration class:

@Configuration
public class AppConfig {
    @Bean
    @Singleton
    public Logger logger() {
        return Logger.getInstance(); // your existing singleton
    }
}

4. Avoiding Common Pitfalls

  • Double‑Checked Locking without volatile – the classic pattern can suffer from visibility issues on pre‑Java 5 JVMs. Always declare the instance field private static volatile Singleton INSTANCE; if you opt for this approach.
  • Over‑Eager Initialization in Classpath Scanning – frameworks that scan annotations may trigger class loading earlier than expected, turning a lazy singleton into an eager one inadvertently. Be aware of when your class is loaded (e.g., during component scanning).
  • Memory Leaks via ClassLoaders – if a singleton is loaded by a custom classloader and the application repeatedly creates new classloaders (common in OSGi or plugin architectures), each loader will hold its own copy of the singleton. Consider using the system classloader or a shared parent loader to keep a single instance across modules.

5. When to Favor Alternatives

While singletons solve the “single source of truth” problem, they are not always the best fit:

  • Immutable Configuration Objects – a simple final static holder (public static final Config CONFIG = new Config();) provides the same accessibility without the need for a getter method.
  • Stateless Utility Classes – if the object holds no state, a utility class with private constructor and static methods can be used, eliminating concerns about instance duplication altogether.
  • Context‑Scoped Objects – in web applications, request‑ or session‑scoped beans often serve the same purpose as a singleton but with a lifecycle tied to the user interaction, reducing the risk of stale data lingering across requests.

6. Documentation and Contract Enforcement

Make the singleton contract explicit in Javadoc:

/**
 * Provides a globally accessible logger. The class follows the singleton
 * pattern: construction is private, access is via {@link #getInstance()},
 * and the instance is created lazily in a thread‑safe manner.
 *
 * 

Implementation notes: * ```java /** * Provides a globally accessible logger. The class follows the singleton * pattern: construction is private, access is via {@link #getInstance()}, * and the instance is created lazily in a thread‑safe manner. * *

Implementation notes: *

    *
  • Thread safety – The {@link #getInstance()} method guarantees * safe publication of the singleton even on highly concurrent * runtimes. The recommended approach is the enum pattern, * which the compiler handles correctly and avoids subtle * synchronization pitfalls.
  • *
  • Serialization – To preserve the singleton guarantee across * JVM restarts, implement {@link java.io.Serializable#readObject} * and return the existing instance via a {@code readObject} method * (or the {@code readResolve} helper). This prevents a new instance * from being created during deserialization.
  • *
  • Cloning – Override {@link Cloneable} and throw * {@link CloneNotSupportedException} in the private {@code clone} * method to forbid accidental duplication.
  • *
  • Reflection attacks – A malicious caller could bypass the * private constructor using reflection. Guard against this by * checking the call stack in the constructor or by using a * {@code sun.reflect.ReflectionPermission} check. The enum pattern * is immune to such attacks.
  • *
* * @see #getInstance() * @see java.io.Serializable */

7. Testing the Singleton in a Spring Boot Context

When the singleton is exposed as a Spring bean (as shown in §3), standard testing tools such as JUnit 5 and Mockito can verify its behavior:

@ExtendWith(SpringExtension.class)
@SpringBootTest
class LoggerTest {

    @Autowired
    private Logger logger;          // injected bean

    @Test
    void singleInstanceIsReturned() {
        Logger other = Logger.getInstance();
        assertSame(logger, other);
    }

    @Test
    void beanIsSingletonByDefault() {
        ApplicationContext ctx = ...In real terms, ; // obtain via @Autowired if needed
        assertSame(ctx. getBean(Logger.Which means class),
                    ctx. getBean(Logger.

Because the Spring container manages the bean, you can also mock it in
unit tests without worrying about the underlying singleton implementation.

### 8. Integration with Spring’s Lifecycle Callbacks
If the singleton needs to perform initialization or cleanup, consider
implementing Spring’s `InitializingBean` and `DisposableBean` interfaces
(or using `@PostConstruct` / `@PreDestroy`). Example:

```java
@Configuration
public class LoggerConfig {

    @Bean
    @Singleton
    public Logger logger() {
        Logger instance = Logger.getInstance();
        // custom initialization
        instance.initialize();
        return instance;
    }
}
public class Logger {
    // … existing singleton code …

    @PostConstruct
    private void initialize() {
        // start background threads, open resources, etc.
    }

    @PreDestroy
    private void shutdown() {
        // clean up resources
    }
}

Spring will invoke these callbacks exactly once, preserving the singleton contract while giving you full control over startup and shutdown.

9. Best‑Practice Checklist

  • Thread safety – Use the enum pattern or a well‑tested double‑checked locking with volatile. Avoid custom synchronization unless you have a compelling reason The details matter here. Less friction, more output..

  • Visibility – Declare the instance field volatile (or rely on the memory model guarantees of the enum pattern) to ensure other threads see the fully‑initialized object.

  • Serialization – Implement readObject/readResolve to maintain the singleton across serialization boundaries.

  • **Cl

  • Clone prevention – Override clone() to throw CloneNotSupportedException if the class implements Cloneable, or avoid Cloneable entirely. The enum pattern is inherently resistant to cloning because enum constants are instantiated by the JVM and cannot be duplicated That alone is useful..

  • Class loading – Remember that multiple class loaders can load the same singleton class into separate namespaces, technically producing more than one instance. In a standard Spring Boot application this is rarely a concern, but it becomes relevant in OSGi containers, application servers, or highly modular deployments Simple as that..

  • Documentation – Explicitly document the singleton contract in Javadoc so callers understand they are interacting with a shared, globally accessible instance rather than a regular, independently instantiable object.

10. Conclusion

Singletons remain a useful tool when a single, coordinated instance is genuinely required—such as a logger, configuration cache, or connection pool manager. In modern Java, the enum-based singleton is the preferred implementation: it is concise, thread-safe, and resistant to reflection, serialization, and cloning attacks without extra boilerplate Took long enough..

When running inside Spring, the container’s own bean management often supersedes hand-rolled singletons. Still, declaring a class with @Component or exposing it through @Bean gives you the same single-instance semantics while adding dependency injection, lifecycle callbacks, and easier testing. Use @PostConstruct and @PreDestroy (or InitializingBean/DisposableBean) to handle initialization and cleanup without breaking the singleton contract.

For testing, rely on JUnit and Mockito to verify that injected beans and direct getInstance() calls resolve to the same object, and mock the singleton where appropriate to isolate unit tests from global state.

Finally, treat singletons as a deliberate design choice rather than a default. Global state can complicate concurrency, testing, and modularity, so apply the pattern only when true single-instance semantics are required, and document that decision clearly for anyone who maintains the code The details matter here. That's the whole idea..

Out This Week

Recently Written

These Connect Well

Others Found Helpful

Thank you for reading about How To Make Class Singleton In Java. We hope the information has been useful. Feel free to contact us if you have any questions. See you next time — don't forget to bookmark!
⌂ Back to Home