Java Enum Constants: Declaration, Behavior, and Use
java enum constants: Learn how to declare, extend, and use Java enum constants — from basic syntax to constant-specific behavior, EnumSet, and common pitfalls.
Declaring Enum Constants
Java enum constants are the named values declared inside an enum type. The simplest declaration looks like this:
public enum OrderStatus { PENDING, PROCESSING, SHIPPED, DELIVERED }
Each identifier — PENDING, PROCESSING, SHIPPED, DELIVERED — is a constant instance of OrderStatus. The compiler generates a final class that extends java.lang.Enum, and each constant is a static final field of that class. This gives enum constants two important properties: there is exactly one instance of each constant per JVM, and identity comparison with == is both safe and the recommended way to compare them.
Adding State to Enum Constants
Enums are not limited to bare labels. Each constant can carry fields, and the constructor runs once per constant at class initialization:
public enum OrderStatus { PENDING("Order received"), PROCESSING("Being prepared"), SHIPPED("In transit"), DELIVERED("Completed"); private final String description; OrderStatus(String description) { this.description = description; } public String description() { return description; } }
The constructor is implicitly private, so no external code can create additional instances. This is what preserves the singleton guarantee. Fields should be final when the value never changes after construction; if you need mutable state per constant, keep it private and expose it through methods rather than making the field public.
Using Enum Constants in Switch Statements
Switch statements are the most common place enum constants appear in day-to-day code. Since Java 14, the arrow-style switch expression works cleanly with enums:
public String describe(OrderStatus status) { return switch (status) { case PENDING -> "Waiting for payment"; case PROCESSING -> "Being packed"; case SHIPPED -> "On the way"; case DELIVERED -> "Arrived"; }; }
The compiler checks exhaustiveness: if you add a new constant to OrderStatus and forget to handle it here, the switch expression will fail to compile. That is a meaningful advantage over if-else chains, which silently ignore new constants until a runtime bug surfaces.
Constant-Specific Behavior
When the behavior of each constant differs beyond simple data, you can give each constant its own implementation of an abstract method:
public enum Discount { NONE { @Override public double apply(double price) { return price; } }, TEN_PERCENT { @Override public double apply(double price) { return price * 0.9; } }; public abstract double apply(double price); }
Each constant gets its own anonymous subclass, so method dispatch is resolved at compile time. This keeps the logic co-located with the constant that owns it. Use this pattern when the set of constants is stable and the behavior is genuinely tied to the constant; for frequently changing rules, a strategy class may be easier to maintain than editing the enum repeatedly.
EnumSet and EnumMap
For collections of enum constants, the standard library provides two specialized classes. EnumSet is a high-performance Set implementation backed by a bit vector:
EnumSet<OrderStatus> active = EnumSet.of(OrderStatus.PENDING, OrderStatus.PROCESSING);
EnumMap is a Map implementation backed by an array indexed by the constant's ordinal:
EnumMap<OrderStatus, String> labels = new EnumMap<>(OrderStatus.class); labels.put(OrderStatus.SHIPPED, "In transit");
Both reject null keys and are significantly more memory-efficient than their generic counterparts because they avoid hashing and boxing. They are the right choice whenever the key set is an enum.
Common Mistakes and Edge Cases
The most frequent error is calling valueOf with a name that does not exist:
OrderStatus.valueOf("CANCELLED"); // throws IllegalArgumentException
This is fine when the input is known to be valid, but for user input you should catch the exception or iterate values() to find a match. A second common mistake is relying on ordinal() for persistence or ordering. The ordinal is the position in the declaration order; inserting a new constant in the middle shifts every subsequent ordinal, which silently corrupts any stored value. Store the constant name as a String instead.
Null is another edge case. Enum constants are objects, so a variable of an enum type can hold null. A switch on a null enum throws NullPointerException, so validate input before dispatching.
Maintainability Considerations
Enum constants are a form of compile-time configuration. Adding a constant is a source-level change that forces the compiler to recheck every switch expression and every exhaustive pattern match. That is the main maintainability tradeoff: the compiler catches omissions, but it also means a new constant can break a build until every consumer is updated.
Keep the declaration order meaningful. The order defines ordinal() and the iteration order of values(), so group related constants together and document any ordering contract. Avoid adding fields that duplicate information already available elsewhere, and prefer final fields over mutable state unless the constant genuinely needs to change at runtime.