EnumSet and EnumMap rely on ordinal() internally. What risk does this create for your own code that depends on enum ordering, and how do you avoid it?
answer
- ordinal = declaration position, name = identifier string
- EnumSet/EnumMap use ordinal transiently -> safe
- persisting ordinal = silent corruption on reorder/insert
- persist name() or an explicit code(), not ordinal
- JPA: prefer EnumType.STRING over ORDINAL
basics
~20 sEnumSet/EnumMap use ordinal positions internally, which is fine and stays correct. The danger is in YOUR code: if you store or persist ordinal numbers and then reorder or insert enum constants, those numbers now point at the wrong constant. Persist the constant name, not its ordinal.
solid answer
~50 sInternally EnumSet and EnumMap index by ordinal(), and that is perfectly safe because the array/bit positions are derived freshly from the current enum definition at runtime — reordering constants just changes both sides together. The real hazard is when YOUR code captures an ordinal and stores it somewhere durable: a database column, a serialized blob, a wire protocol, or a config file. Ordinals are positional and unstable: inserting a constant in the middle or reordering the declaration silently shifts every later ordinal, so old stored numbers now map to different constants — a quiet data-corruption bug. The fix is to never persist ordinals; persist the constant's name() (stable as long as you do not rename it) or an explicit, declared code/id field on the enum, and convert with valueOf or a lookup map. Reserve ordinal() for in-memory, transient uses where the enum definition cannot change underneath you.
code
java · 11 linesenum Status {
NEW(1), ACTIVE(2), CLOSED(3); // explicit stable codes
private final int code;
Status(int code) { this.code = code; }
public int code() { return code; }
private static final Map<Integer, Status> BY_CODE =
Arrays.stream(values()).collect(Collectors.toMap(Status::code, s -> s));
public static Status fromCode(int c) { return BY_CODE.get(c); }
}
// Persist status.code(), restore Status.fromCode(c) -- safe to reorder constants.go deeper
Knows ordinal() returns the position and that storing it is risky; would store the name instead.
Explains how inserting/reordering constants shifts ordinals and corrupts persisted data, and uses name() or EnumType.STRING.
Distinguishes EnumSet/EnumMap's safe transient ordinal use from unsafe persisted ordinals, and designs an explicit stable code() with a lookup map for storage.
Sets org-wide conventions (no ordinal persistence, explicit codes for cross-service contracts, migration strategy for legacy ordinal columns) and reasons about enum evolution in versioned APIs/events.
## What ordinal() is Every enum constant has an **ordinal** — its zero-based position in the order the constants are declared. `Day.MONDAY.ordinal()` is 0 if `MONDAY` is declared first. The `name()` method, by contrast, returns the constant's identifier as written (`"MONDAY"`). ## How EnumSet/EnumMap use it — and why that is safe These collections are efficient precisely because they map each constant to an integer index = its ordinal: EnumSet sets bit *ordinal* in a bit vector; EnumMap stores the value at `array[ordinal]`. This is **internal and transient**: the bit vector or array is built at runtime from the *current* enum definition. If you reorder the constants and recompile/redeploy, both the ordinals and the collection's indexing change **together**, so the collection stays correct. There is no stored, stale ordinal to go wrong. So the collections' use of ordinal is not the problem. ## The real danger: persisting ordinals in YOUR code The trap is when application code treats the ordinal as a **stable identifier** and writes it somewhere that outlives the running JVM: - a database column storing `status.ordinal()`, - Java serialization or a custom binary format keyed by ordinal, - a network/wire protocol field, - a cache or message that another version of the app reads. Ordinals are **positional**, so they are unstable across code changes. Consider: ```java enum Status { NEW, ACTIVE, CLOSED } // ordinals 0,1,2 // later someone inserts a constant: enum Status { NEW, PENDING, ACTIVE, CLOSED } // ACTIVE is now 2, CLOSED is 3 ``` Every row that stored `2` to mean `CLOSED` now decodes as `ACTIVE`, and `3` is out of range or wrong. Nothing fails loudly — it is **silent data corruption**. Even just reordering the declaration causes the same shift. ## How to avoid it 1. **Never persist `ordinal()`.** Persist the **name** (`status.name()`, restore with `Status.valueOf(s)`), which is stable until someone renames the constant. 2. **Even safer: a declared stable code.** Give the enum an explicit immutable id and map on it, decoupling persistence from both position *and* spelling: ```java enum Status { NEW(1), ACTIVE(2), CLOSED(3); private final int code; Status(int code) { this.code = code; } public int code() { return code; } private static final Map<Integer, Status> BY_CODE = Arrays.stream(values()).collect(toMap(Status::code, s -> s)); public static Status fromCode(int c) { return BY_CODE.get(c); } } ``` Now you can reorder or insert constants freely; only the explicit `code` matters for storage. 3. **JPA/Hibernate note:** `@Enumerated(EnumType.ORDINAL)` (often the default behavior people fall into) persists the ordinal and has exactly this fragility; prefer `@Enumerated(EnumType.STRING)` or a converter to an explicit code. 4. **Keep `ordinal()` for transient, in-process uses only** — array indexing, EnumSet/EnumMap (which do it for you), or local comparisons within one run. ## Mental model Treat `ordinal()` like a memory address: meaningful only within the current process/definition, never as a durable key. EnumSet/EnumMap honor that rule (transient use); bugs come from your code breaking it.
- Why is EnumMap's internal use of ordinal() safe even though persisting ordinals is dangerous?EnumMap builds its array from the current enum definition at runtime, so the index and the constant change together; nothing stale is stored. Danger only arises when an ordinal is written somewhere durable and later read against a changed enum.
- Between name() and an explicit code() field, which is more robust for persistence and why?An explicit code() is most robust: it survives both reordering AND renaming the constant. name() survives reordering but breaks if someone renames the constant, so it is a good default but slightly less safe than a declared code.
An ordinal is like a seat number in a row of chairs: fine while you're in the room, but if someone adds a chair in the middle, every saved 'seat 3' note now points at the wrong person.
saying these in an interview costs you the question
- Claiming EnumSet/EnumMap are unsafe because they use ordinals (they use them transiently and are fine).
- Persisting status.ordinal() to a DB or wire format.
- Using @Enumerated(EnumType.ORDINAL) for long-lived data.
- Believing valueOf works on the ordinal number (it takes the name string).