Course contents

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.

PremiumOften askedLesson18 min readCoreReviewed Oct 2026 · Java 21
On this page
  1. Picture it first: a vending machine with fixed buttons
  2. What the compiler generates under the hood
  3. switch on enums, and what javac does with it
  4. Real world: EnumMap, EnumSet and the ordinal trap
  5. Advanced: valueOf, singletons and the static-field trap
  6. How to say it in the interview

🚦 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 javac generates for an enum: a final class extending java.lang.Enum, static final constants, values() and valueOf()
  • How to add fields, constructors, methods and constant-specific bodies, and how enums implement interfaces
  • Why EnumSet is a bit vector and EnumMap an 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 handle valueOf failures 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 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:

Fig. 1

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

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:

  1. It's an ordinary final class extending java.lang.Enum<OrderStatus>. That's why an enum can't extend anything else, and why you can't subclass it.
  2. Each constant is a public static final field. The static initializer (static {}) calls new OrderStatus("NEW", 0), new OrderStatus("PAID", 1) and so on. The name and position go up to Enum's protected constructor, where name() and ordinal() read them.
  3. The constructor is private, whatever you write. Declaring an enum constructor public or protected is a compile error.
  4. 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 a private static final field.
  5. valueOf(String) delegates to Enum.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

Practise this topicRecords, enums & Optional: interview questions & scenarios
Further reading (8)