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 finalto 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 returningfalseif you compare mismatched types. - Always give a
switchon an enum adefaultcase, or use exhaustive handling, so the code fails loudly if a new constant is ever added. - Prefer
EnumSetandEnumMapoverHashSet/HashMapwhen the keys are enum constants — they are faster and use far less memory. - Keep enum fields
private finaland expose them only through getters, so each constant stays immutable. - Use the constant-specific method body pattern instead of a large
switch/if-elsechain when behavior genuinely differs per constant. - Never rely on
ordinal()for persistence, serialization, or business logic; use an explicit field orname()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
TrafficLightwith constantsRED,YELLOW,GREEN. Add a methodnext()that returns the following light in the cycleRED → GREEN → YELLOW → RED. Print the sequence for 6 calls starting fromRED. - Exercise 2: Create an enum
Coinwith constantsPENNY,NICKEL,DIME,QUARTER, each carrying its value in cents via a constructor. Write a method that sums the total value of an array ofCoinvalues and prints the result in dollars (expected output for oneQUARTER, twoDIME, onePENNY:$0.46). - Exercise 3: Create an enum
Vehiclethat implements an interfaceTaxablewith a methoddouble 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(), andordinal()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/EnumMapfor enum-keyed collections for better performance and clarity.
