An enum defines a fixed set of named constants, but those constants are ordinary initialization-time objects rather than values that exist before their enum class is initialized. This matters when constructors compute fields from other enum constants: a reference can observe the other enum before its constants are ready and permanently receive null. section 3.9.4
enum Pet {
DOG(Voice.BARK),
CAT(Voice.MEOW);
final Voice voice;
Pet(Voice voice) { this.voice = voice; }
}
enum Voice {
BARK(Pet.DOG),
MEOW(Pet.CAT);
final Pet owner;
Voice(Pet owner) { this.owner = owner; }
}
These mutually dependent constructors form an initialization loop. If Pet starts first, Voice is still uninitialized; if Voice starts first, Pet is still uninitialized. One direction may appear to work while the reverse field is null. section 3.9.4
Avoid constructor-time cycles between enums. If a relationship must be established this way, make the affected field nonfinal and assign it after constructor invocation, when both enum constant sets exist. Do not assume enum utilities are safe during constant construction: values() is unavailable until all constants have been created. section 3.9.4
A switch over the enum currently being initialized can cause the same failure on Java 20 and earlier. Such switches may implicitly invoke values(), because compilation desugars an enum switch to a numerical switch with a mapping based on ordinal values. The resulting class initialization can throw NullPointerException. Use ordinal()-based logic only where that trade-off is explicitly intended, or avoid switching on the current enum during its construction. section 3.9.4
Enums also model a meaningful finite state better than null. When a method can return “no value” with more than one meaning, encode each meaning as a named constant such as UNKNOWN, UNSPECIFIED, or UNINITIALIZED. The caller can then handle every outcome directly rather than infer what a null meant. section 5.1.1
Model a three-way user decision
A nullable Boolean can blur Yes, No, and Cancel; treating null as false can accidentally continue an operation after cancellation. Define an enum such as UserAnswer { YES, NO, CANCEL }, or model the requested action directly with REMOVE_FROM_BASKET, MOVE_TO_WISH_LIST, and CANCEL. A switch can then perform the action or deliberately do nothing for cancellation. RoundingMode.UNNECESSARY similarly gives “do not round” an explicit name and documented consequence. section 5.1.1






















