A guide to Java enums
What an enum compiles into, fields and constant-specific behaviour, interfaces and switch, EnumSet and EnumMap internals, the enum singleton, the ordinal and valueOf traps, enums with sealed types, and a state machine.
On this page
🚦 What is an enum?
An enum is a class with a fixed, named set of instances that the JVM creates once and never more. enum OrderStatus { NEW, PAID, SHIPPED, DELIVERED, CANCELLED } gives you exactly five OrderStatus objects for the life of the program, so a method that takes an OrderStatus can never receive "SHIPPPED" or 7. Each constant can carry fields, methods and even its own behaviour.
Enums look like a beginner topic, and that's exactly why interviewers use them. "What does the compiler generate for an enum?", "Why is an enum the best singleton?", "What goes wrong if you store ordinal() in the database?" and "Why is EnumSet faster than HashSet?" all have short answers that only people who've looked underneath can give.
What you’ll learn
- What
javacgenerates for an enum: a final class extendingjava.lang.Enum, static final constants,values()andvalueOf() - How to add fields, constructors, methods and constant-specific bodies, and how enums implement interfaces
- Why
EnumSetis a bit vector andEnumMapan ordinal-indexed array, and what that buys you - Why a single-element enum is a serialization- and reflection-safe singleton
- Why you must never persist
ordinal(), and how to handlevalueOffailures safely - When to use an enum versus a sealed interface of records, and how to model a state machine
For this lesson, we'll focus on what an enum compiles into, fields and constructors, constant-specific behaviour, interfaces, switch, EnumMap and EnumSet, the enum singleton, the ordinal and valueOf traps, and how enums fit with sealed types and pattern matching in Java 21. We'll finish with a small order state machine.
Below the line (out of scope)
- The full
switchexpression and pattern syntax: covered in Pattern matching for switch and if, switch and the switch expression - Sealed hierarchies in depth: covered in Sealed classes and interfaces
- Reflection internals: covered in A guide to Java reflection
The 30-second answer
An enum is a class whose instances are fixed at compile time. javac turns it into a class that extends java.lang.Enum, with one public static final field per constant, a private constructor, and generated values() and valueOf(String) methods. The constants are created once during class initialisation, so they're thread-safe singletons and can be compared with ==. Enums can have fields, constructors, methods, constant-specific bodies and interfaces. EnumSet is a bit vector and EnumMap an ordinal-indexed array, both much faster than hash-based collections. Don't persist ordinal(), because reordering constants silently corrupts data; store name() or an explicit code instead.
Interview radar
Asked very often: What does an enum compile to, can it have constructors and methods, and why can you compare enums with ==?
Senior rounds probe: Why the enum singleton is safe against reflection and serialization, how EnumSet and EnumMap work internally, and why persisting ordinal() is dangerous.
Production angle: Someone inserts a new constant in the middle of an enum and every row stored by ordinal now maps to the wrong status.
01Picture it first: a vending machine with fixed buttons
Think of an enum as a vending machine with a fixed row of buttons. There are exactly five buttons, each with a printed label (the name) and a position on the panel (the ordinal). You can't add a button by shouting a new label at the machine, and every customer presses the same physical button for "Coffee", not a copy of it.
That last point matters. A String constant like "PAID" is a value anyone can misspell or invent. An enum constant is a specific object, and the type system guarantees only the declared ones exist. That's the real upgrade over the old "int enum pattern" (public static final int STATUS_PAID = 1;): type safety, a namespace, a useful toString, and a place to put behaviour.
Here's what javac turns a short enum into:
On the left a three-constant enum; on the right the generated final class extending Enum, with a static final field per constant pointing to one heap object each holding a name and ordinal, a private $VALUES array, and generated values() and valueOf() methods
02What the compiler generates under the hood
Try it yourself first
Before reading on, write down what you think javap -p OrderStatus.class shows for this three-constant enum.
public enum OrderStatus { NEW, PAID, SHIPPED }
public final class OrderStatus extends java.lang.Enum<OrderStatus> {
public static final OrderStatus NEW;
public static final OrderStatus PAID;
public static final OrderStatus SHIPPED;
private static final OrderStatus[] $VALUES;
public static OrderStatus[] values();
public static OrderStatus valueOf(java.lang.String);
private OrderStatus();
private static OrderStatus[] $values();
static {};
}
Notice five things:
- It's an ordinary
finalclass extendingjava.lang.Enum<OrderStatus>. That's why an enum can't extend anything else, and why you can't subclass it. - Each constant is a
public static finalfield. The static initializer (static {}) callsnew OrderStatus("NEW", 0),new OrderStatus("PAID", 1)and so on. The name and position go up toEnum's protected constructor, wherename()andordinal()read them. - The constructor is
private, whatever you write. Declaring an enum constructorpublicorprotectedis a compile error. values()returns$VALUES.clone(), a fresh array on every call, because arrays are mutable and the JDK can't hand out its internal copy. In a hot loop, cache it in aprivate static finalfield.valueOf(String)delegates toEnum.valueOf(Class, String), which looks the name up in a map the JDK builds once per enum class.
Because the constants are created during class initialisation, the JVM's class-init lock makes their creation thread-safe for free. That fact powers the enum singleton later in this lesson.
java.lang.Enum also fixes some behaviour so you can't break it. equals and hashCode are final and identity-based, compareTo compares ordinals, clone() throws CloneNotSupportedException, and name() is final. You can override toString(), which is why name() is the one to use for anything machine-readable.
Fields, constructors and methods
Try it yourself first
Before reading on: how would you attach a fee percentage to each payment method, so that adding a new method can't forget its fee?
public enum PaymentMethod {
CARD("Credit/debit card", 180), // fee in basis points: 1.80%
UPI("UPI", 0),
NET_BANKING("Net banking", 90);
private final String label;
private final int feeBps;
PaymentMethod(String label, int feeBps) { // implicitly private
this.label = label;
this.feeBps = feeBps;
}
public long feeFor(long amountMinor) {
return amountMinor * feeBps / 10_000;
}
public String label() { return label; }
}
System.out.println(PaymentMethod.CARD.feeFor(50_000)); // prints: 900
System.out.println(PaymentMethod.UPI.label()); // prints: UPI
Notice that the constructor arguments sit right next to each constant. Adding WALLET without a fee doesn't compile, which is exactly the kind of mistake you want the compiler to catch. Keep enum fields final: an enum constant is a global singleton, and a mutable field on it is global mutable state shared by every thread.
Constant-specific bodies
When behaviour differs per constant, you can give each constant its own class body that overrides an abstract method:
public enum Discount {
NONE { long apply(long total) { return total; } },
FLAT_100 { long apply(long total) { return Math.max(0, total - 100_00); } },
PERCENT_10 { long apply(long total) { return total - total / 10; } };
abstract long apply(long totalMinor);
}
System.out.println(Discount.PERCENT_10.apply(2_000_00)); // prints: 180000
Under the hood, each constant with a body becomes an anonymous subclass (Discount$1, Discount$2, ...), and the enum class itself is no longer final. Since Java 17 the JLS calls such an enum implicitly sealed: its only permitted subclasses are those constant bodies. The call Discount.PERCENT_10.apply(...) compiles to an invokevirtual that dispatches through the vtable, the same as any override.
This is the strategy pattern without the ceremony. If you see a switch (this) inside an enum method, that's often a sign a constant-specific body or a constructor field would be cleaner, because a new constant would then be forced to provide its behaviour.
Premium lesson
Keep reading with the Java Pass
The rest of this lesson is part of Java One-Stop Interview Prep: in-depth explanations, diagrams, model answers and how to say them in the interview.
- switch on enums, and what javac does with it
- Real world: EnumMap, EnumSet and the ordinal trap
- Advanced: valueOf, singletons and the static-field trap
- How to say it in the interview
From ₹199 · one-time payment · No auto-renewal
Further reading (8)
- JLS §8.9 Enum Classes
- java.lang.Enum javadoc (Java 21)
- java.util.EnumSet javadoc (Java 21)
- java.util.EnumMap javadoc (Java 21)
- JEP 441: Pattern Matching for switch
- The Java Tutorials: Enum Types
- Effective Java, 3rd ed., Items 3, 34 to 38 (enum singleton, enums instead of int constants, EnumSet, EnumMap)
- Baeldung: A Guide to Java Enums