Singleton Pattern
Java Design Patterns
Java Programming
Software Development
Object-Oriented Design

Singleton with Arguments in Java

Master System Design with Codemia

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

Introduction

Singleton with constructor arguments is a common request when one global service needs startup configuration such as endpoint URLs, credentials, or cache settings. The challenge is ensuring initialization happens exactly once and remains thread-safe. A robust design separates one-time initialization from later access, and fails loudly on invalid reconfiguration attempts.

Why Argument-Based Singleton Is Tricky

A basic singleton with no arguments is straightforward. Add arguments, and you must answer:

  • who is allowed to initialize it.
  • what happens if code calls initialization twice with different values.
  • how to guarantee correctness under concurrency.

If these rules are undefined, singleton state can become nondeterministic across environments.

Thread-Safe Initialization Pattern

A practical approach is explicit initialize plus getInstance, guarded by synchronization and idempotency checks.

java
1public final class AppConfigSingleton {
2    private static volatile AppConfigSingleton instance;
3
4    private final String apiBaseUrl;
5    private final int timeoutMs;
6
7    private AppConfigSingleton(String apiBaseUrl, int timeoutMs) {
8        this.apiBaseUrl = apiBaseUrl;
9        this.timeoutMs = timeoutMs;
10    }
11
12    public static void initialize(String apiBaseUrl, int timeoutMs) {
13        if (instance != null) {
14            throw new IllegalStateException("Already initialized");
15        }
16        synchronized (AppConfigSingleton.class) {
17            if (instance == null) {
18                instance = new AppConfigSingleton(apiBaseUrl, timeoutMs);
19            } else {
20                throw new IllegalStateException("Already initialized");
21            }
22        }
23    }
24
25    public static AppConfigSingleton getInstance() {
26        AppConfigSingleton ref = instance;
27        if (ref == null) {
28            throw new IllegalStateException("Not initialized");
29        }
30        return ref;
31    }
32
33    public String getApiBaseUrl() {
34        return apiBaseUrl;
35    }
36
37    public int getTimeoutMs() {
38        return timeoutMs;
39    }
40}

This design avoids lazy argument ambiguity and gives clear failure modes.

Startup Wiring Example

Initialize once during application bootstrap, then read from anywhere.

java
1public class Main {
2    public static void main(String[] args) {
3        String apiBaseUrl = System.getenv("API_BASE_URL");
4        int timeoutMs = Integer.parseInt(System.getenv().getOrDefault("API_TIMEOUT_MS", "3000"));
5
6        AppConfigSingleton.initialize(apiBaseUrl, timeoutMs);
7
8        Service service = new Service();
9        service.run();
10    }
11}
12
13class Service {
14    public void run() {
15        AppConfigSingleton cfg = AppConfigSingleton.getInstance();
16        System.out.println("Using " + cfg.getApiBaseUrl() + " timeout=" + cfg.getTimeoutMs());
17    }
18}

This keeps initialization ownership in one place and avoids hidden global mutation.

Alternative: Dependency Injection Instead of Singleton

In modern Java applications, a DI container often provides singleton lifecycle and constructor injection without manual static state. If you already use Spring or similar frameworks, prefer container-managed singletons:

  • cleaner testability.
  • explicit dependencies.
  • easier environment-based configuration.

Manual singleton with arguments is most defensible in lightweight apps without DI frameworks.

Validation Rules for Initialization Arguments

When initialization parameters come from environment variables or external files, validate them before creating the singleton instance. Reject invalid URLs, empty credentials, or out-of-range numeric values immediately. Early validation prevents partially configured global state that can fail later in unrelated code paths.

Testing Considerations

Static singleton state can leak between tests. Add a controlled reset method only in test scope, or isolate tests in separate JVM forks.

Example test-only reset:

java
1// package-private for tests
2static void resetForTests() {
3    synchronized (AppConfigSingleton.class) {
4        instance = null;
5    }
6}

Without this, test order can affect results and hide production issues.

Common Pitfalls

  • Allowing multiple initialization calls and silently ignoring configuration mismatch.
  • Returning default singleton before required arguments are validated.
  • Missing thread safety around first initialization.
  • Overusing static singleton where DI container would provide cleaner architecture.
  • Letting singleton state leak across unit tests.

Summary

  • Singleton with arguments requires explicit one-time initialization rules.
  • Use synchronized or equivalent thread-safe initialization patterns.
  • Separate initialize from getInstance for clear lifecycle boundaries.
  • Prefer DI-managed singletons when framework support exists.
  • Design test strategy to avoid static-state leakage between test cases.

Course illustration
Course illustration

All Rights Reserved.