Java Enums

An enum (short for enumeration) is a special Java type that represents a fixed, predefined set of constant values — like the days of the week, the suits in a deck of cards, or the states of a traffic light. Enums replace error-prone tricks like using raw integers or strings for a closed set of options with a type-safe, self-documenting alternative that the compiler actively checks for you. Under the hood, an enum is a real class, which means it can have fields, constructors, methods, and even implement interfaces, making it far more powerful than a simple list of names.

Overview / How Enums Work

When you write enum Day { MONDAY, TUESDAY, WEDNESDAY }, the Java compiler generates a full class named Day that implicitly extends java.lang.Enum<Day>. Each constant (MONDAY, TUESDAY, …) becomes a public static final instance of that class, created exactly once when the enum class is first loaded by the JVM. Because every constant is a singleton object, you can safely compare enum values with == instead of .equals() — there is only ever one Day.MONDAY object in the entire JVM, no matter how many times you reference it.

Since Enum already extends Object, and Java does not support multiple class inheritance, an enum cannot extend any other class. However, it can implement interfaces, which is a common and powerful pattern. Every enum automatically inherits useful methods from java.lang.Enum, including name(), ordinal(), compareTo(), and toString(), and the compiler generates two additional static methods for you: values() and valueOf(String).

Method Description
values() Returns an array of all constants, in declaration order
valueOf(String name) Returns the constant matching the given name, or throws IllegalArgumentException
name() Returns the exact constant name as declared
ordinal() Returns the zero-based position of the constant in its declaration
compareTo(E other) Compares based on ordinal position
toString() Returns the constant name by default; can be overridden

Syntax

enum EnumName {
    CONSTANT_ONE, CONSTANT_TWO, CONSTANT_THREE;

    // fields
    private final Type field;

    // constructor (implicitly private)
    EnumName(Type field) {
        this.field = field;
    }

    // methods
    public Type getField() {
        return field;
    }
}
  • Constant list — the comma-separated names in ALL_CAPS by convention; each becomes a public static final instance.
  • Semicolon — required after the last constant only if the enum body also contains fields, constructors, or methods.
  • Constructor — always implicitly private (or package-private); you cannot call it yourself. It runs once per constant, at class-loading time.
  • Fields/methods — ordinary class members; fields are typically private final to keep constants immutable.

Examples

Example 1: A basic enum with a switch statement

public class Main {
    enum Day {
        MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
    }

    public static void main(String[] args) {
        Day today = Day.WEDNESDAY;
        System.out.println("Today is " + today);

        switch (today) {
            case SATURDAY:
            case SUNDAY:
                System.out.println("It's the weekend!");
                break;
            default:
                System.out.println("It's a weekday.");
        }
    }
}

Output:

Today is WEDNESDAY
It's a weekday.

Notice that inside a switch on an enum you write the bare constant name (SATURDAY), not Day.SATURDAY — the compiler already knows the type from the switch expression. Printing today directly calls the inherited toString(), which returns the constant’s name.

Example 2: Enum with fields, a constructor, and a method

public class Main {
    enum Planet {
        MERCURY(3.303e+23, 2.4397e6),
        VENUS(4.869e+24, 6.0518e6),
        EARTH(5.976e+24, 6.37814e6);

        private final double mass;
        private final double radius;

        Planet(double mass, double radius) {
            this.mass = mass;
            this.radius = radius;
        }

        double surfaceGravity() {
            final double G = 6.67300E-11;
            return G * mass / (radius * radius);
        }
    }

    public static void main(String[] args) {
        for (Planet p : Planet.values()) {
            System.out.printf("%s gravity: %.2f%n", p, p.surfaceGravity());
        }
    }
}

Output:

MERCURY gravity: 3.70
VENUS gravity: 8.87
EARTH gravity: 9.80

Each constant carries its own mass and radius, supplied through the constructor when the constant is declared. Because the constructor is called once per constant at class-loading time, every Planet value is immutable and ready to use immediately — there is no way to create a Planet object anywhere else in the program.

Example 3: Abstract method implemented per constant

public class Main {
    enum Operation {
        PLUS {
            public int apply(int x, int y) { return x + y; }
        },
        MINUS {
            public int apply(int x, int y) { return x - y; }
        },
        TIMES {
            public int apply(int x, int y) { return x * y; }
        };

        public abstract int apply(int x, int y);
    }

    public static void main(String[] args) {
        int x = 6, y = 3;
        for (Operation op : Operation.values()) {
            System.out.printf("%d %s %d = %d%n", x, op, y, op.apply(x, y));
        }
    }
}

Output:

6 PLUS 3 = 9
6 MINUS 3 = 3
6 TIMES 3 = 18

This is the constant-specific method body pattern. Declaring apply as abstract in the enum body forces every single constant to supply its own { ... } implementation. Behind the scenes, the compiler generates an anonymous subclass of Operation for each constant (PLUS, MINUS, TIMES), each overriding apply differently. This is a clean, type-safe replacement for a big switch statement full of behavior.

Under the Hood

When the JVM first references an enum type, class loading kicks in and runs a hidden static initializer that constructs every constant, in declaration order, exactly once, and stores them in a hidden static array. This is why enum construction is thread-safe by default — the JVM’s class-loading mechanism guarantees the constants are fully built before any code can observe them, and no other code can invoke the (implicitly private) constructor to create duplicates. This same guarantee is why enums are the recommended way to implement the Singleton pattern in Java.

values() works by returning a defensive copy of that hidden array every time it is called, so mutating the returned array never corrupts the enum’s real constants. ordinal() simply returns the index into that declaration-order array, and compareTo() subtracts ordinals. Because enums extend Enum, they also automatically get a correct, singleton-based equals() and hashCode(), and they implement Comparable and Serializable out of the box.

Java also provides two collections specialized for enums: EnumSet and EnumMap. Both are backed by bitmasks or arrays indexed by ordinal() internally, which makes them dramatically faster and more memory-efficient than a general-purpose HashSet or HashMap when your keys are enum constants.

Common Mistakes

Mistake 1: Forgetting the semicolon after the last constant

As soon as you add anything besides constants — a field, constructor, or method — the constant list must end with a semicolon. This is easy to forget and produces a confusing compiler error.

public class Main {
    enum Status {
        ACTIVE,
        INACTIVE

        public String describe() {
            return "Status: " + name();
        }
    }

    public static void main(String[] args) {
        System.out.println(Status.ACTIVE.describe());
    }
}

This fails to compile because the parser cannot tell where the constant list ends and the method body begins without the terminating semicolon. The fix is simple:

public class Main {
    enum Status {
        ACTIVE,
        INACTIVE;

        public String describe() {
            return "Status: " + name();
        }
    }

    public static void main(String[] args) {
        System.out.println(Status.ACTIVE.describe());
    }
}

Output:

Status: ACTIVE

Mistake 2: Persisting or comparing by ordinal()

It’s tempting to save ordinal() to a database or file to represent an enum value compactly, but ordinal() is just the constant’s position in the source code. If anyone ever reorders, inserts, or removes a constant, every previously stored ordinal now points to the wrong value — silently.

public class Main {
    enum Priority {
        LOW, MEDIUM, HIGH
    }

    public static void main(String[] args) {
        int savedPriority = Priority.HIGH.ordinal(); // saved as 2
        System.out.println("Saved priority code: " + savedPriority);

        // If a new constant is later inserted at the front, e.g.
        // enum Priority { URGENT, LOW, MEDIUM, HIGH }
        // then ordinal() for HIGH becomes 3, and the old saved
        // value 2 now silently refers to MEDIUM instead of HIGH.
        System.out.println("Looked up name: " + Priority.values()[savedPriority]);
    }
}

Output:

Saved priority code: 2
Looked up name: HIGH

The safe fix is to attach an explicit, stable value to each constant instead of relying on declaration order:

public class Main {
    enum Priority {
        LOW(10), MEDIUM(20), HIGH(30);

        private final int level;

        Priority(int level) {
            this.level = level;
        }

        public int getLevel() {
            return level;
        }
    }

    public static void main(String[] args) {
        int savedPriority = Priority.HIGH.getLevel(); // saved as 30
        System.out.println("Saved priority code: " + savedPriority);

        for (Priority p : Priority.values()) {
            if (p.getLevel() == savedPriority) {
                System.out.println("Looked up name: " + p);
            }
        }
    }
}

Output:

Saved priority code: 30
Looked up name: HIGH

Now inserting or reordering constants can never change the stored meaning of 30.

Best Practices

  • Use == to compare enum constants, not .equals() — it is safe (every constant is a singleton), faster, and gives a compile-time type error instead of silently returning false if you compare mismatched types.
  • Always give a switch on an enum a default case, or use exhaustive handling, so the code fails loudly if a new constant is ever added.
  • Prefer EnumSet and EnumMap over HashSet/HashMap when the keys are enum constants — they are faster and use far less memory.
  • Keep enum fields private final and expose them only through getters, so each constant stays immutable.
  • Use the constant-specific method body pattern instead of a large switch/if-else chain when behavior genuinely differs per constant.
  • Never rely on ordinal() for persistence, serialization, or business logic; use an explicit field or name() instead.
  • Implement interfaces on enums when you need to treat several unrelated enums polymorphically through a shared contract.

Practice Exercises

  • Exercise 1: Create an enum TrafficLight with constants RED, YELLOW, GREEN. Add a method next() that returns the following light in the cycle RED → GREEN → YELLOW → RED. Print the sequence for 6 calls starting from RED.
  • Exercise 2: Create an enum Coin with constants PENNY, NICKEL, DIME, QUARTER, each carrying its value in cents via a constructor. Write a method that sums the total value of an array of Coin values and prints the result in dollars (expected output for one QUARTER, two DIME, one PENNY: $0.46).
  • Exercise 3: Create an enum Vehicle that implements an interface Taxable with a method double taxRate(), giving each constant (e.g. CAR, MOTORCYCLE, TRUCK) a different rate using constant-specific method bodies. Print the calculated tax for a $20,000 purchase for each constant.

Summary

  • An enum is a compiler-generated class whose constants are public static final singleton instances created once at class-loading time.
  • Enums can have fields, constructors, and methods, and can implement interfaces, but cannot extend another class.
  • values(), valueOf(), name(), and ordinal() are built in; use == for comparisons since constants are singletons.
  • The constant-specific method body pattern lets each constant override an abstract method, replacing large switch statements with clean polymorphism.
  • Never persist or branch logic on ordinal(); attach explicit fields to constants instead.
  • Prefer EnumSet/EnumMap for enum-keyed collections for better performance and clarity.