Enums

A Java enum is a full class, not just a named integer — it can carry fields, constructors, methods, and even per-constant method bodies, all while guaranteeing that each constant is a single, fixed, comparable-by-identity instance.

Java enums represent a fixed, known set of constant values as a proper type, not a loose collection of int codes. Each enum constant is a genuine singleton instance of the enum class, which unlocks type safety, exhaustive switch handling, and — because enums are real classes — fields, constructors, and behavior attached directly to the type.

Basic enum Declaration

An enum declaration lists its constants, separated by commas, typically in uppercase by convention. Each named constant is automatically a public static final instance of the enum type — there is no need to write that boilerplate yourself.

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

public class Main {
    public static void main(String[] args) {
        Day today = Day.WEDNESDAY;
        System.out.println(today); // WEDNESDAY (default toString: constant name)
    }
}

Enums Are Classes Under the Hood

An enum implicitly extends java.lang.Enum<T>, making it a real class — it just cannot extend any other class (Java's single-inheritance rule already spends the one superclass slot on Enum), and it is implicitly final unless it declares constant-specific class bodies (covered below). Because it's a class, an enum can implement interfaces, declare fields, constructors, and methods exactly like any other class.

Fields, Constructors, and Methods

Enum constants can carry data by giving the enum fields and a constructor; each constant supplies its constructor arguments in parentheses right after its name. The constructor is implicitly private (or package-private) — it can never be called with new from outside the enum, since the complete set of instances is exactly the listed constants.

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

    private final double mass;   // kg
    private final double radius; // m

    Planet(double mass, double radius) { // implicitly private constructor
        this.mass = mass;
        this.radius = radius;
    }

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

public class Main {
    public static void main(String[] args) {
        System.out.println(Planet.EARTH.surfaceGravity());
    }
}

values() and valueOf()

The compiler automatically generates two static methods for every enum: values() returns an array of all constants in declaration order, and valueOf(String name) returns the constant with the exact given name, throwing IllegalArgumentException if no such constant exists.

for (Day d : Day.values()) {
    System.out.println(d);
}

Day parsed = Day.valueOf("FRIDAY"); // exact name match required
// Day.valueOf("friday"); // throws IllegalArgumentException -- case-sensitive

ordinal() and name()

Every enum constant has an ordinal() — its zero-based position in the declaration order — and a name() — its exact declared identifier as a String. ordinal() is useful for compact array indexing in specific low-level scenarios but is fragile: reordering the enum's declared constants silently changes every ordinal, which can corrupt anything (like a persisted database value) that depended on the old ordering. Prefer name() or an explicit field for anything that must remain stable.

System.out.println(Day.WEDNESDAY.ordinal()); // 2 (zero-based: MONDAY=0, TUESDAY=1, WEDNESDAY=2)
System.out.println(Day.WEDNESDAY.name());     // "WEDNESDAY"

Enum Identity and ==

Because each enum constant is guaranteed to be a single instance — the JVM never creates a second Day.WEDNESDAY — comparing enum values with == is both safe and idiomatic, unlike with most reference types where == usually should be avoided in favor of equals(). Enum inherits a final equals() from java.lang.Enum that is itself defined purely in terms of identity, so equals() and == behave identically for enum constants.

Day a = Day.WEDNESDAY;
Day b = Day.valueOf("WEDNESDAY");
System.out.println(a == b);       // true: same singleton instance
System.out.println(a.equals(b));  // true: Enum.equals() is identity-based too

Enums in switch

switch works especially well with enums: case labels use the bare constant name (no enum-type prefix), and the compiler can warn or, with a switch expression over a sealed-like exhaustive set (Java 14+), require handling every constant.

static String describe(Day d) {
    return switch (d) {
        case SATURDAY, SUNDAY -> "Weekend";
        case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "Weekday";
    };
}

Constant-Specific Method Bodies

An enum constant can override a method for just that one constant by supplying a class body in braces right after its constructor arguments — this compiles to an anonymous subclass of the enum for that specific constant. This is how enums can carry genuinely different per-constant behavior without an external switch statement.

Each constant supplies its own apply() implementation. Because at least one constant has a class body, the enum itself cannot be declared final by the compiler -- each such constant is compiled as an anonymous subclass.

enum Operation {
    PLUS {
        @Override public int apply(int a, int b) { return a + b; }
    },
    MINUS {
        @Override public int apply(int a, int b) { return a - b; }
    },
    TIMES {
        @Override public int apply(int a, int b) { return a * b; }
    };

    public abstract int apply(int a, int b);
}

public class Main {
    public static void main(String[] args) {
        for (Operation op : Operation.values()) {
            System.out.println(op + ": " + op.apply(6, 2));
        }
    }
}

Enums Implementing Interfaces

An enum can implement one or more interfaces, letting it be used polymorphically wherever that interface is expected — combined with constant-specific bodies, this is a clean, type-safe alternative to a large if/else or switch chain scattered across the codebase.

interface Describable {
    String describe();
}

enum Status implements Describable {
    ACTIVE { public String describe() { return "Currently active"; } },
    PAUSED { public String describe() { return "Temporarily paused"; } },
    CLOSED { public String describe() { return "Permanently closed"; } }
}

EnumSet and EnumMap

The Collections Framework provides EnumSet and EnumMap, specialized, highly efficient implementations built specifically for enum keys/elements — internally represented as compact bit vectors or arrays indexed by ordinal, rather than general-purpose hashing. They are strongly preferred over HashSet<EnumType>/HashMap<EnumType, V> whenever the key/element type is an enum.

import java.util.EnumSet;
import java.util.EnumMap;

EnumSet<Day> weekend = EnumSet.of(Day.SATURDAY, Day.SUNDAY);
EnumMap<Day, String> plans = new EnumMap<>(Day.class);
plans.put(Day.FRIDAY, "Movie night");

Enum Instances Cannot Be Created with new

The set of instances of an enum type is completely fixed by its declaration; new EnumType(...) is not legal anywhere outside the enum's own constant declarations, even from within the enum's own methods. This is precisely what guarantees the singleton-per-constant property that makes == comparisons safe.

Practical Usage

Enums are the right tool whenever a value genuinely represents one of a small, fixed, known set of options at compile time — days of the week, HTTP status categories, order states, card suits, configuration modes. They replace the older, error-prone pattern of using plain int or String "codes," giving compile-time type safety, meaningful names in stack traces and logs, and IDE auto-completion of valid values.

Edge Cases

Because enum's equals()/hashCode() are identity-based and final, an enum constant can safely be used as a HashMap key or HashSet element without any risk of the mutable-key hazard that plagues ordinary mutable objects (see Equality & Hashing) — though EnumMap/EnumSet are still generally preferable for performance.

An enum can declare abstract methods (as with Operation.apply() above) only if every single constant supplies a constant-specific body implementing it — otherwise the compiler reports an error for the constants left without an implementation.

Serialization of enums (java.io.Serializable) has special, well-defined semantics: an enum constant is always serialized by name, never by its fields, so custom serialization logic is rarely, if ever, needed for enums.

Best Practices

Prefer enums over int/String constants for any fixed, closed set of values. Use constant-specific method bodies instead of an external switch when different constants genuinely need different behavior. Avoid relying on ordinal() for anything persisted or compared across versions of the code — use name() or an explicit stable field instead. Reach for EnumSet/EnumMap rather than general-purpose collections when working with enum keys or elements.

Key Takeaways

An enum is a real class extending java.lang.Enum, with each named constant an automatically-created public static final singleton instance.

Enums can have fields, constructors (implicitly non-public), and methods, and can implement interfaces exactly like ordinary classes.

values() lists all constants in declaration order; valueOf(String) looks one up by exact name and throws IllegalArgumentException if not found.

Enum constants are singletons, so == and the inherited, identity-based equals() behave identically and safely for comparing them.

A constant-specific class body lets an individual constant override a method with its own implementation, without an external switch.

EnumSet and EnumMap are specialized, efficient collection implementations designed specifically for enum types and are preferred over general-purpose HashSet/HashMap for enum keys.

enum Status { ACTIVE, PAUSED, CLOSED } // later, persisting the status as its ordinal: int code = Status.PAUSED.ordinal(); // 1 // ...if a new constant is later inserted before PAUSED, this stored '1' now means something else!

enum Status { ACTIVE, PAUSED, CLOSED } // persist by name instead: String code = Status.PAUSED.name(); // "PAUSED" -- stable even if declaration order changes Status restored = Status.valueOf(code);

ordinal() reflects declaration order, which is fragile -- inserting, removing, or reordering constants silently shifts every subsequent ordinal. name() (or an explicit stable field/code) survives reordering safely.

enum Suit { HEARTS, SPADES, CLUBS, DIAMONDS } Suit s = new Suit(); // does not compile

Suit s = Suit.HEARTS; // reference one of the fixed, predeclared constants

Enum instances cannot be created with new anywhere outside the enum's own constant declarations; the complete set of instances is exactly the constants listed in the enum body.

enum Operation { PLUS, MINUS; // no constant-specific bodies public abstract int apply(int a, int b); // compile error: not every constant implements it } enum Operation { PLUS { public int apply(int a, int b) { return a + b; } }, MINUS { public int apply(int a, int b) { return a - b; } }; public abstract int apply(int a, int b); }

If an enum declares an abstract method, every single constant must supply a constant-specific class body implementing it -- there is no default or partial implementation allowed.

What is the actual superclass of every Java enum, and what constraint does this place on enum declarations?

java.lang.Enum<T>. Because Java allows only single class inheritance and that one slot is already used by Enum, an enum declaration cannot extend any other class (though it can implement interfaces).

Why is comparing enum constants with == considered safe and idiomatic, unlike most reference types?

Each enum constant is guaranteed by the language to be a single singleton instance -- the JVM never creates a second instance of, say, Day.MONDAY -- so reference identity (==) reliably distinguishes and compares enum constants correctly, and Enum's inherited equals() is itself defined identically, purely by identity.

What does Day.valueOf("friday") do, given an enum Day with a constant FRIDAY?

It throws IllegalArgumentException at runtime, because valueOf() requires an exact, case-sensitive match to a declared constant's name, and "friday" does not match "FRIDAY".

Why is relying on ordinal() for anything persisted (like a database value) considered fragile?

ordinal() reflects a constant's zero-based position in declaration order. Inserting, removing, or reordering constants in the enum's source changes the ordinals of constants after that point, silently corrupting any previously stored ordinal-based value. name() or an explicit stable field is safer for persistence.

What is a constant-specific method body, and what does it compile to?

A class body attached to an individual enum constant (in braces after its constructor arguments) that overrides one or more methods just for that constant. It compiles to an anonymous subclass of the enum type specific to that constant.

Why are EnumSet and EnumMap preferred over HashSet<EnumType> / HashMap<EnumType, V>?

EnumSet and EnumMap are specialized implementations, typically backed by compact bit vectors or arrays indexed by the constants' ordinals, making them significantly more memory- and time-efficient than general-purpose hash-based collections for enum keys or elements.

Interview-style: why can't you call 'new SomeEnum()' anywhere, even from inside the enum's own code?

The entire, fixed set of an enum's instances is defined exclusively by its declared constants -- this closed-instance-set guarantee is precisely what makes identity comparison (==) and switch exhaustiveness reliable. Allowing arbitrary construction anywhere would undermine that guarantee, so the compiler forbids new for enum types entirely, including from within the enum's own methods.

Write an enum TrafficLight with constants RED, YELLOW, GREEN, and a method next() that returns the next light in the standard cycle (RED -> GREEN -> YELLOW -> RED).

enum TrafficLight { RED, YELLOW, GREEN; TrafficLight next() { return switch (this) { case RED -> GREEN; case GREEN -> YELLOW; case YELLOW -> RED; }; } } public class Main { public static void main(String[] args) { TrafficLight light = TrafficLight.RED; for (int i = 0; i < 4; i++) { System.out.println(light); light = light.next(); } } }

Prints RED, GREEN, YELLOW, RED. The switch expression over 'this' (the current constant) is exhaustive over all three TrafficLight constants, so no default branch is needed.

What does this print, and why is the first comparison guaranteed to be correct even though a and b came from different expressions?

Prints true, then 1. Size.valueOf("MEDIUM") always returns the exact same singleton instance as the constant Size.MEDIUM, because an enum type's complete set of instances is fixed at exactly its declared constants -- there is only ever one MEDIUM object to return. ordinal() is 1 because MEDIUM is the second constant declared (zero-based: SMALL=0, MEDIUM=1).

Write an enum Coin implementing a Valued interface (with an int cents() method), giving PENNY, NICKEL, DIME, and QUARTER each their correct constant-specific cents() value.

interface Valued { int cents(); } enum Coin implements Valued { PENNY { public int cents() { return 1; } }, NICKEL { public int cents() { return 5; } }, DIME { public int cents() { return 10; } }, QUARTER{ public int cents() { return 25; } } } public class Main { public static void main(String[] args) { int total = 0; for (Coin c : Coin.values()) { total += c.cents(); } System.out.println(total); // 41 } }

Each constant supplies its own cents() implementation via a constant-specific class body, and because Coin implements Valued, every constant can be used polymorphically wherever a Valued is expected. Summing all four gives 1 + 5 + 10 + 25 = 41.

What does this print, and why is it safe to use an enum as a HashMap key without worrying about the mutable-key hazard covered in the Equality & Hashing topic?

Prints true. Enum constants are singletons with a final, identity-based equals()/hashCode() inherited from java.lang.Enum that can never be overridden to depend on mutable state, and enums have no mutable fields exposed for external mutation by design -- so there is no way for an enum constant's hash code to change after it's used as a key, unlike an ordinary mutable object.

enum Size { SMALL, MEDIUM, LARGE }

public class Main {
    public static void main(String[] args) {
        Size a = Size.MEDIUM;
        Size b = Size.valueOf("MEDIUM");
        System.out.println(a == b);
        System.out.println(a.ordinal());
    }
}
enum Level { LOW, MEDIUM, HIGH }

public class Main {
    public static void main(String[] args) {
        java.util.HashMap<Level, String> labels = new java.util.HashMap<>();
        labels.put(Level.LOW, "Not urgent");
        System.out.println(labels.containsKey(Level.valueOf("LOW")));
    }
}