Singleton accessor with a reachable constructor

ID

apex.maintainability.singleton_private_constructor

Severity

high

Remediation Complexity

trivial

Remediation Risk

medium

Remediation Effort

low

Resource

Api Design

Language

Apex

Description

Reports a class exposing a static getInstance accessor that returns its own type while declaring no private constructor, so callers can still build instances with new.

Rationale

A getInstance accessor is a promise that one instance exists per transaction, and code written against it relies on that: the cached custom settings, the accumulated DML rows, the "already ran" flag that keeps a trigger from recursing. As long as the constructor is reachable, any caller can bypass the accessor with new, get a second instance with its own empty state, and the guarantee quietly stops holding. In a trigger this shows up as the recursion guard failing to guard, and in a cache as a second round of SOQL against the same governor limits.

The following code illustrates the pattern detected by this rule:

// FLAGGED: Singleton accessor with a reachable constructor
public class SettingsCache {

    private static SettingsCache instance;
    private Map<String, String> values = new Map<String, String>();

    public SettingsCache() {
        values.put('mode', 'default');
    }

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

    public String get(String key) {
        return values.get(key);
    }
}

Remediation

Declare the constructor private so the accessor is the only way in, and keep the instance in a private static field. Where the class needs to be built differently in tests, keep the constructor private and mark it @TestVisible rather than widening it.

Configuration

This detector does not need any configuration.

© 2026 Xygeni. All rights reserved