Java EnumMap with Examples: Usage, Streams and vs HashMap

Java EnumMap stores enum keys in an array by ordinal. Learn its constructors, null rules, key order, stream collectors and how it differs from HashMap.

An EnumMap stores the value for each enum constant in an array slot equal to the constant's ordinal

EnumMap is a specialized Map whose keys are the constants of one enum type, stored in an array indexed by each constant’s position in the enum. It keeps the entries in the order in which the constants are declared, and it never hashes a key.

We use an EnumMap whenever the keys of a map are enum constants, such as a person responsible for each ticket priority, a counter per order status or a timeout per environment. It replaces a HashMap with enum keys in all of these cases.

The following example maps ticket priorities to the support engineers who handle them.

enum Priority { LOW, MEDIUM, HIGH, CRITICAL }
EnumMap<Priority, String> owners = new EnumMap<>(Priority.class);
owners.put(Priority.HIGH, "alex");
owners.put(Priority.LOW, "bob");
owners.put(Priority.CRITICAL, "lokesh");

String all = owners.toString();                          // "{LOW=bob, HIGH=alex, CRITICAL=lokesh}"
String high = owners.get(Priority.HIGH);                 // "alex"
boolean hasMedium = owners.containsKey(Priority.MEDIUM); // false
String removed = owners.remove(Priority.LOW);            // "bob"
int size = owners.size();                                // 2

Notice that toString() lists LOW first although we added it second, because the map follows the declaration order of Priority. We look at how the class stores keys, the constructors and methods, the differences from HashMap, collecting streams into an EnumMap, and a ticket-routing example.

1. How EnumMap Stores Its Keys

Every enum constant has an ordinal, which is its position in the declaration, starting at 0. An EnumMap allocates an array with one slot per constant when we create it, and put(Priority.HIGH, v) writes v into slot Priority.HIGH.ordinal(), which is slot 2. A lookup reads that slot, so no hashCode(), no bucket and no equals() are involved.

An EnumMap stores the value for each enum constant in an array slot equal to the constant's ordinal
The value for HIGH goes to slot 2 because HIGH.ordinal() is 2, and empty slots mean no mapping

The class has been part of the JDK since Java 5. Several of its rules differ from a HashMap.

  • All keys come from a single enum type, which the constructor fixes through its Class object.
  • Iteration follows the declaration order of the constants, not the insertion order.
  • A null key is not allowed, while null values are.
  • Iterators are weakly consistent, so they never throw ConcurrentModificationException.
  • The class is not synchronized, and it implements Serializable and Cloneable.

2. Working with EnumMap

An EnumMap supports every Map method, including the Java 8 defaults such as merge(), computeIfAbsent() and getOrDefault(). The only new requirement is the key type at creation time.

2.1. Creating EnumMap

The class has three constructors. The first one takes the Class object of the enum, because Java erases generic types at runtime and the map needs to know how many slots to allocate.

EnumMap<Priority, Integer> empty = new EnumMap<>(Priority.class);                   // {}
EnumMap<Priority, Integer> copyOfEnumMap = new EnumMap<>(empty);                    // same key type, can be empty
EnumMap<Priority, Integer> fromMap = new EnumMap<>(Map.of(Priority.HIGH, 4));       // {HIGH=4}
EnumMap<Priority, Integer> fromEmpty = new EnumMap<>(new HashMap<Priority, Integer>());   // IllegalArgumentException

The constructor that takes a general Map reads the key type from the first key. An empty HashMap has no first key, so the constructor throws an IllegalArgumentException with the message “Specified map is empty”. We use new EnumMap<>(Priority.class) followed by putAll() when the source map can be empty.

2.2. EnumMap Methods

The usual Map methods behave as expected, and the views returned by keySet(), values() and entrySet() follow the declaration order. A common use is a counter per constant with merge().

EnumMap<Priority, Integer> openTickets = new EnumMap<>(Priority.class);
openTickets.merge(Priority.HIGH, 1, Integer::sum);
openTickets.merge(Priority.LOW, 1, Integer::sum);
openTickets.merge(Priority.HIGH, 1, Integer::sum);

int highCount = openTickets.get(Priority.HIGH);                         // 2
int criticalCount = openTickets.getOrDefault(Priority.CRITICAL, 0);     // 0
Set<Priority> keys = openTickets.keySet();                              // [LOW, HIGH]
Collection<Integer> counts = openTickets.values();                      // [1, 2]
boolean hasTwo = openTickets.containsValue(2);                          // true
openTickets.replaceAll((p, n) -> n * 10);
String scaled = openTickets.toString();                                 // "{LOW=10, HIGH=20}"

To start every constant at a value, we loop over values() of the enum once. After that, every get() returns a number, and reports show the constants with zero tickets too.

EnumMap<Priority, Integer> zeroed = new EnumMap<>(Priority.class);
for (Priority p : Priority.values()) {
    zeroed.put(p, 0);
}
String allZero = zeroed.toString();                                     // "{LOW=0, MEDIUM=0, HIGH=0, CRITICAL=0}"

3. Difference Between Java EnumMap and HashMap

Both classes accept enum keys, but an EnumMap is designed for them. Creation, null keys and iteration order are the differences that change our code.

FeatureEnumMapHashMap
Key typeConstants of one enumAny object
StorageArray indexed by ordinal()Hash table with buckets
Iteration orderDeclaration order of the constantsNot defined
null keyRejected by put() with NullPointerExceptionOne null key allowed
IteratorWeakly consistentFail-fast
Lookup costOne array accessHash, bucket search and equals()

3.1. Creation

An EnumMap needs the key class in the constructor, whereas a HashMap needs nothing. The diamond operator still infers the generic types on the left side.

Map<Priority, String> enumBased = new EnumMap<>(Priority.class);
Map<Priority, String> hashBased = new HashMap<>();

3.2. Null Keys

An enum constant can never be null, so an EnumMap rejects a null key in put(). Reads with a null key are allowed and report that no entry exists, which keeps lookup code with optional input simple.

EnumMap<Priority, String> teams = new EnumMap<>(Priority.class);
teams.put(Priority.LOW, null);                           // null values are allowed
String none = teams.get(null);                           // null
boolean hasNull = teams.containsKey(null);               // false
String bad = teams.put(null, "alex");                    // NullPointerException

3.3. Internal Implementations

The EnumMap keeps two arrays, the enum constants of the key type and the values, both with one slot per constant. A missing mapping is an empty slot, and a null value is stored with an internal marker object so that the two cases stay apart. A HashMap instead creates a node object for each entry and links nodes that land in the same bucket, as the HashMap internals article explains.

3.4. Iteration Follows Declaration Order

Iteration over an EnumMap always follows the declaration order, no matter in which order we add the keys. Reordering the constants in the enum source changes the iteration order, so a report sorted by priority stays correct only while the enum lists the constants from lowest to highest.

EnumMap<Priority, String> ordered = new EnumMap<>(Priority.class);
ordered.put(Priority.CRITICAL, "lokesh");
ordered.put(Priority.LOW, "bob");
Priority firstKey = ordered.keySet().iterator().next();   // LOW

The class does not implement the SequencedMap interface from Java 21, so it has no firstEntry() or reversed(). When we need those methods, we copy the entries into a TreeMap, which sorts enum keys by ordinal as well.

3.5. Performance

An EnumMap does less work per call than a HashMap, because get() and put() read the ordinal and access one array slot. It has no hash collisions, no resizing and no node objects, so it also uses less memory. In most apps the speed difference is small next to the rest of the code, and the stronger reasons to choose EnumMap are the fixed order and the clear intent.

4. Collecting a Stream into an EnumMap

The collectors groupingBy() and toMap() return a HashMap by default. Both have an overload that takes a map supplier, and () -> new EnumMap<>(Priority.class) makes them return an EnumMap, which keeps the result in priority order. The method reference EnumMap::new does not work here, because the constructor needs the key class.

record Ticket(String title, Priority priority) {}
List<Ticket> tickets = List.of(
        new Ticket("Login fails", Priority.CRITICAL),
        new Ticket("Typo on page", Priority.LOW),
        new Ticket("Slow search", Priority.HIGH),
        new Ticket("Broken link", Priority.LOW));

Map<Priority, Long> perPriority = tickets.stream()
        .collect(Collectors.groupingBy(Ticket::priority,
                () -> new EnumMap<>(Priority.class), Collectors.counting()));
String grouped = perPriority.toString();                 // "{LOW=2, HIGH=1, CRITICAL=1}"

Map<Priority, String> firstTitle = tickets.stream()
        .collect(Collectors.toMap(Ticket::priority, Ticket::title, (a, b) -> a,
                () -> new EnumMap<>(Priority.class)));
String titles = firstTitle.toString();                   // "{LOW=Typo on page, HIGH=Slow search, CRITICAL=Login fails}"

The merge function (a, b) -> a keeps the first title for each priority. Without it, toMap() throws an IllegalStateException for the second LOW ticket.

5. Routing Support Tickets by Priority

A help desk app reads its routing rules from a configuration file, and each priority has a response time and a team. The operations team changes the values without a new release, so the values cannot live as constants inside the enum. An EnumMap holds the loaded rules, and a missing entry falls back to a default.

static Duration responseTime(Ticket ticket) {
    return RESPONSE_TIME.getOrDefault(ticket.priority(), Duration.ofDays(1));
}

static final EnumMap<Priority, Duration> RESPONSE_TIME = new EnumMap<>(Map.of(
        Priority.CRITICAL, Duration.ofMinutes(15),
        Priority.HIGH, Duration.ofHours(4),
        Priority.LOW, Duration.ofDays(3)));
Duration urgent = responseTime(new Ticket("Login fails", Priority.CRITICAL));   // PT15M
Duration normal = responseTime(new Ticket("Old browser", Priority.MEDIUM));      // PT24H

When the value for each constant is fixed in code, a field on the enum or a switch expression is simpler than any map. An EnumMap fits values that are loaded, counted or changed at runtime, such as these response times or the ticket counts in section 4.

6. EnumMap FAQs

Switching a map with enum keys from HashMap to EnumMap raises these questions.

6.1. Is EnumMap thread-safe?

No. We wrap it with Collections.synchronizedMap(new EnumMap<>(Priority.class)) for shared use. For many concurrent writers, a ConcurrentHashMap with enum keys is the alternative, but it loses the declaration order.

6.2. Is EnumMap ordered?

Yes. Its iteration order is the declaration order of the enum constants, which is also the order of ordinal(). The order does not depend on when we add the keys.

6.3. What is the difference between EnumMap and EnumSet?

An EnumMap maps each constant to a value, whereas an EnumSet only records which constants are present. Both use the ordinal, and an EnumSet stores the constants as a bit vector, which makes it very compact and fast.

6.4. Can we iterate an EnumMap and modify it at the same time?

Yes. The iterators are weakly consistent, so removing or adding a mapping during a loop does not throw ConcurrentModificationException. The loop may or may not see the change, depending on the slot.

6.5. Why does EnumMap need the Class object in the constructor?

Generic types are erased at runtime, so new EnumMap<>() cannot know the enum type or the number of constants. The Class object gives the map both, and it lets the map reject keys of another enum with a ClassCastException.

7. Conclusion

An EnumMap stores one value per enum constant in an array indexed by the ordinal. It needs the enum class at creation, rejects null keys in put(), iterates in declaration order and never throws ConcurrentModificationException from its iterators.

For maps with enum keys, it is the default choice over HashMap, and the supplier overloads of groupingBy() and toMap() produce it from streams. When the values never change, a field on the enum is simpler still. We list EnumMap and EnumSet with the other implementations in our Java Collections guide.

8. References

Happy Learning !!

Source Code on Github

About Us

HowToDoInJava provides tutorials and how-to guides on Java and related technologies.

It also shares the best practices, algorithms & solutions and frequently asked interview questions.