Skip to main content

Enums as Classes and the Enum Singleton Idiom

Medium

Interview Question: "Unlike in C/C++, Java enums are fully-fledged classes. How do constructors, fields, and methods work in Java enums, and why does Joshua Bloch famously advocate the Enum Singleton idiom over traditional Double-Checked Locking (DCL) or Bill Pugh holder singletons?"

The short version​

In Java, enum isn't just a list of named integers like in C. Under the hood it's a real class - each constant (SUCCESS, FAILED, ...) is a public static final object of that class, created once when the class loads. Because it's a real object, it can have fields, a constructor, and its own methods, just like any other class.

That same property makes enums the cleanest way to write a Singleton. A single-constant enum gets thread safety, protection from reflection, and safe serialization for free - all three of which you'd otherwise have to hand-roll yourself.


Part 1: enums can hold real state, not just names​

If an enum were just a label, you'd end up writing scattered switch statements everywhere to decide what each value "means." Instead, you can give each constant its own fields and behavior directly:

public enum TransactionStatus {
// Each constant calls the constructor once, when the class loads
SUCCESS(200, "Payment cleared successfully") {
@Override
public boolean isTerminal() {
return true;
}
},
FAILED(400, "Insufficient funds or declined") {
@Override
public boolean isTerminal() {
return true;
}
},
PENDING(202, "Awaiting banking network confirmation") {
@Override
public boolean isTerminal() {
return false;
}
};

private final int statusCode;
private final String description;

// constructors on enums are implicitly private - you can't call this yourself
TransactionStatus(int statusCode, String description) {
this.statusCode = statusCode;
this.description = description;
}

public int getStatusCode() {
return this.statusCode;
}

public String getDescription() {
return this.description;
}

// each constant overrides this with its own answer
public abstract boolean isTerminal();
}
TransactionStatus status = TransactionStatus.PENDING;
System.out.println("HTTP Code: " + status.getStatusCode()); // 202
System.out.println("Terminal? " + status.isTerminal()); // false

Part 2: why an enum makes the best Singleton​

Before this idiom, people built singletons two ways:

  1. Double-Checked Locking (DCL) - a volatile field plus synchronized blocks, to stop two threads from both creating an instance at once.
  2. Bill Pugh / static holder - a nested static class that the JVM's class-loading rules happen to initialize lazily and safely.

Both work, but both have the same two holes:

  • Reflection can call the private constructor directly via setAccessible(true) and spin up a second instance.
  • Serialization can reconstruct a second instance on deserialize, unless you remember to write a readResolve() method.

A single-constant enum closes both holes automatically, because the JVM treats enum construction and enum deserialization as special cases:

Traditional singleton
Constructor.newInstance()
reflection can call this directly
deserialize(bytes)
creates a new object unless you write readResolve()
Enum singleton
Constructor.newInstance()
JVM throws IllegalArgumentException - blocked
deserialize(bytes)
JVM looks up the existing constant by name - no new object

Joshua Bloch (Effective Java, Item 3) puts it simply: "A single-element enum type is the best way to implement a singleton."

public enum DatabaseConnection {
INSTANCE; // the one and only instance

private final String connectionUrl;
private boolean connected;

// runs exactly once, when the enum class is first loaded
DatabaseConnection() {
this.connectionUrl = "jdbc:postgresql://db.prod.internal:5432/wallet";
this.connected = true;
}

public void executeQuery(String sql) {
if (!connected) throw new IllegalStateException("Database disconnected");
System.out.println("Executing on " + connectionUrl + ": " + sql);
}
}
DatabaseConnection.INSTANCE.executeQuery("SELECT * FROM users WHERE active = true");

Enum Singleton vs. the older approaches​

DimensionEnum SingletonDCL SingletonBill Pugh Holder
Thread safetyGuaranteed by the JVM at class loadManual, via synchronized + volatileGuaranteed by the JVM at class load
Lazy loadingMostly - loads when the enum is first referencedYes, on first getInstance() callYes, when the holder class is first referenced
Reflection-proofYes - the JVM blocks it outrightNo - needs a manual guard in the constructorNo - needs a manual guard in the constructor
Serialization-safeYes - JVM resolves by name automaticallyNo - needs readResolve()No - needs readResolve()
BoilerplateMinimalSignificantModerate
Can extend a classNo (can implement interfaces)YesYes

Why reflection can't break it​

Even if attacker code grabs the enum's constructor and calls setAccessible(true), the JDK has an explicit guard against it:

import java.lang.reflect.Constructor;

public class ReflectionAttackDemo {
public static void main(String[] args) {
try {
Constructor<DatabaseConnection> constructor =
DatabaseConnection.class.getDeclaredConstructor(String.class, int.class);
constructor.setAccessible(true);
DatabaseConnection secondInstance = constructor.newInstance("FAKE_INSTANCE", 1);
} catch (Exception e) {
// java.lang.IllegalArgumentException: Cannot reflectively create enum objects
System.out.println("Reflection blocked! Reason: " + e.getMessage());
}
}
}

This is baked into the JDK itself (java.lang.reflect.Constructor):

if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");

And for serialization: a normal Serializable class gets reconstructed byte-by-byte on deserialize, which skips the constructor and can produce a second object (s1 == s2 fails unless you wrote readResolve()). Enums skip all of that - only the constant's name gets written to the byte stream, and deserializing calls Enum.valueOf(Class, name) to fetch the one existing instance back. No new object is ever created.


The one real limitation: no extending a class​

Every enum already implicitly extends java.lang.Enum<E>, and Java only allows single class inheritance - so an enum can never extend another custom class (public enum MySingleton extends BaseService won't compile).

It can still implement interfaces, which covers almost every real use case:

public interface CacheService {
void put(String key, Object val);
Object get(String key);
}

public enum RedisCacheService implements CacheService {
INSTANCE;

private final Map<String, Object> localStore = new ConcurrentHashMap<>();

@Override
public void put(String key, Object val) {
localStore.put(key, val);
}

@Override
public Object get(String key) {
return localStore.get(key);
}
}

Say this in the interview​

"Java enums are real classes - each constant is a pre-built public static final instance of that class, so enums can carry fields, constructors, and methods just like any other type. That makes a single-constant enum the cleanest way to write a Singleton: the JVM guarantees it's created exactly once, thread-safely, and it's immune to the two usual ways people break singletons - reflection (the JVM explicitly refuses to reflectively construct an enum) and serialization (the JVM reconstructs it by name lookup instead of allocating a new object). The only real trade-off is that an enum can't extend another class, though it can still implement interfaces."