EnumMap and IdentityHashMap in Java: Two Special Maps Worth Knowing

  • Last Updated: August 10, 2026
  • By: javahandson
  • Series
img

EnumMap and IdentityHashMap in Java: Two Special Maps Worth Knowing

EnumMap and IdentityHashMap in Java explained simply. Learn how each stores keys, when to use them, and the bugs IdentityHashMap can cause. With examples.

1. Introduction

You already know HashMap. You probably know TreeMap and LinkedHashMap too. So why bother with two more map types? Because Java ships a couple of niche maps that solve very specific problems, and EnumMap and IdentityHashMap are two of them.

Most days you won’t touch either one. But when the right situation shows up, these maps do a cleaner job than a plain HashMap ever could. Knowing they exist saves you from writing clumsy workarounds.

In this article we look at both maps side by side. First we take EnumMap, a tiny map built only for enum keys. Then we move to IdentityHashMap, a map that compares keys in an unusual way.

Here’s what we’ll cover:

  • What an EnumMap is, and why it beats HashMap for enum keys
  • How EnumMap stays so fast and so small inside
  • What IdentityHashMap does differently, and why == matters here
  • The kind of bugs IdentityHashMap can quietly cause
  • Where each map fits in real code, with small examples
  • Common traps and a set of interview questions

You only need to know basic maps and enums to follow along. If you’ve used a HashMap before, you’re ready.

EnumMap and IdentityHashMap in Java

2. What Is an EnumMap?

An EnumMap is a map where the keys must be enum constants. That’s the whole rule. You pick one enum type, and only values of that type can be keys.

Think of a weekly planner. The days of the week never change, and there are exactly seven of them. An EnumMap keyed by a Day enum fits that job perfectly.

2.1 A Quick Example

Say you have an enum for days. You want to store a task for each day. An EnumMap makes this clean and safe.

enum Day { MON, TUE, WED, THU, FRI }

EnumMap<Day, String> tasks = new EnumMap<>(Day.class);
tasks.put(Day.MON, "Team standup");
tasks.put(Day.WED, "Code review");
tasks.put(Day.FRI, "Deploy release");

System.out.println(tasks.get(Day.WED)); // Code review
System.out.println(tasks);
// {MON=Team standup, WED=Code review, FRI=Deploy release}

Notice the constructor. You pass Day.class, not an initial size. The EnumMap needs to know the enum type up front, because it uses that type to size itself.

2.2 Keys Stay in Enum Order

Print an EnumMap and the keys come out in the order the enum declares them. MON always comes before WED, no matter when you added them.

This ordering is baked in. You don’t sort anything, and you don’t pay for sorting. The enum’s natural order does the work for free.

2.3 Null Keys Are Not Allowed

You can’t use null as a key in an EnumMap. Try it and you get a NullPointerException. Values can be null, but keys cannot.

That’s usually fine. Enum keys come from a fixed set, so a null key rarely makes sense anyway.

3. How EnumMap Works Internally

Here’s the part that makes EnumMap special. It doesn’t hash anything. There are no buckets, no hash codes, and no collisions.

3.1 It’s Really Just an Array

Every enum constant has a fixed position, given by its ordinal() value. MON is 0, TUE is 1, WED is 2, and so on. An EnumMap uses these numbers as array indexes.

So inside, an EnumMap holds a plain array of values. When you put a key, it drops the value at the slot matching the key’s ordinal. When you get a key, it reads that same slot.

This is why EnumMap is so quick. A get or a put is just an array access. No hashing, no scanning, no bucket walk.

// Rough idea of what EnumMap does inside
// values[] is sized to the number of enum constants

Object[] values;   // one slot per enum constant

// put(key, value)
values[key.ordinal()] = value;

// get(key)
return values[key.ordinal()];  // direct array read

3.2 Why It Uses Less Memory

A HashMap carries extra weight. It keeps an array of buckets, plus a node object for every entry. Each node stores a key, a value, a hash, and a next pointer.

An EnumMap skips all of that. It’s one small array of values, sized exactly to the enum. No nodes, no hash fields, no wasted buckets.

For a map with a handful of enum keys, this saves real memory. It also keeps everything in one tight block, which the CPU loves.

Interview Insight
Q: Why is EnumMap faster than HashMap for enum keys?
A: EnumMap stores values in a plain array, indexed by each enum’s ordinal() value. A get or put is a direct array read or write, with no hashing and no collision handling. A HashMap has to compute a hash, find a bucket, and sometimes walk a chain. For enum keys, the array approach is simpler and faster.

3.3 A Word on the Sibling EnumSet

EnumMap has a cousin called EnumSet. It’s a Set that only holds enum constants. It uses the same ordinal trick, but packs the values into bits.

So if you ever need a set of enum values, EnumSet is the matching tool. It’s tiny and lightning fast, for the same reason EnumMap is.

We won’t dig into EnumSet here. Just remember it exists as the set-shaped partner to EnumMap.

3.4 What Happens If You Add Enum Constants Later

An EnumMap sizes its array from the enum at creation time. Add a new constant to the enum and recompile, and fresh EnumMaps pick up the larger size automatically.

You don’t manage capacity at all. The enum defines how many slots exist, so there’s never a resize like a HashMap does.

4. When to Use EnumMap

The rule is simple. If your keys are enum constants, reach for EnumMap first. It’s the tool built for exactly that job.

4.1 Good Fits

  • Mapping a state to its handler in a state machine, keyed by a State enum.
  • Storing settings per environment, keyed by an Environment enum like DEV, STAGING, PROD.
  • Counting or tracking something per category, when the categories are an enum.
  • Any lookup table where the keys form a small, fixed set.

In all these cases the keys are known ahead of time and never change. That’s the sweet spot for EnumMap.

4.2 A Small Real Example

Say you track order counts by status. The status is an enum, so EnumMap is a natural pick.

enum Status { NEW, PAID, SHIPPED, CANCELLED }

EnumMap<Status, Integer> counts = new EnumMap<>(Status.class);
counts.put(Status.NEW, 12);
counts.put(Status.PAID, 8);
counts.put(Status.PAID, counts.get(Status.PAID) + 1); // bump PAID to 9

System.out.println(counts.get(Status.PAID)); // 9
System.out.println(counts);
// {NEW=12, PAID=9}

Clean, fast, and readable. The keys come out in enum order, so PAID always follows NEW in the printout.

4.3 When to Skip It

Don’t force it. If your keys aren’t enums, EnumMap isn’t an option at all. A plain HashMap is your default for everything else.

Also, EnumMap isn’t thread-safe on its own. For concurrent access, wrap it or use a different approach.

5. What Is an IdentityHashMap?

Now for the stranger one. An IdentityHashMap looks like a normal map, but it compares keys in an unusual way. It uses reference identity instead of equals().

5.1 equals() vs == In One Line

A normal HashMap asks, “are these two keys equal?” using the equals() method. An IdentityHashMap asks, “are these two keys the exact same object?” using ==.

That difference sounds small. In practice it changes everything about how the map behaves.

5.2 A Surprising Example

Two strings can be equal by value but be different objects in memory. Watch how each map treats them.

String a = new String("hello");
String b = new String("hello");

// a.equals(b) is true, but a == b is false

Map<String, Integer> normal = new HashMap<>();
normal.put(a, 1);
normal.put(b, 2);
System.out.println(normal.size()); // 1  (a and b are "equal")

Map<String, Integer> identity = new IdentityHashMap<>();
identity.put(a, 1);
identity.put(b, 2);
System.out.println(identity.size()); // 2  (a and b are different objects)

Look at the sizes. The HashMap sees one key, because a and b are equal by value. The IdentityHashMap sees two keys, because they are separate objects.

This is the core of IdentityHashMap. Value equality means nothing here. Only the object reference counts.

5.3 It Ignores hashCode() Too

IdentityHashMap doesn’t call your key’s hashCode() method. Instead it uses System.identityHashCode(), which is based on the object’s identity, not its fields.

So even if you override equals() and hashCode() on your key class, this map skips them both. It only cares which object you handed it.

Interview Insight
Q: How does IdentityHashMap differ from HashMap?
A: A HashMap compares keys with equals() and hashes them with hashCode(). An IdentityHashMap compares keys with == and hashes them with System.identityHashCode(). So two objects that are equal by value are treated as one key in a HashMap, but as two separate keys in an IdentityHashMap. Reference identity is all that matters.

5.4 How It Stores Entries Inside

IdentityHashMap doesn’t use buckets with linked nodes like HashMap does. It uses a single flat array, with keys and values sitting in alternating slots.

When two keys land on the same spot, it just probes the next slot along. This trick is called open addressing. It keeps the structure simple and compact.

You don’t need to know these details to use the map. Still, it explains why the map feels a bit different from a normal HashMap under load.

5.5 String Literals Can Fool You

Here’s a subtle trap. Java pools string literals, so two identical literals can be the very same object. That makes an IdentityHashMap look like it merges them.

String x = "hi";      // pooled literal
String y = "hi";      // same pooled object, x == y is true

Map<String, Integer> m = new IdentityHashMap<>();
m.put(x, 1);
m.put(y, 2);
System.out.println(m.size()); // 1  (same object!)

// But new String("hi") creates a fresh object each time
String z = new String("hi");
m.put(z, 3);
System.out.println(m.size()); // 2  (z is a different object)

So the same code can behave differently based on how the strings were created. This is exactly why casual use of IdentityHashMap is risky.

6. When to Use IdentityHashMap

This map is rare on purpose. You reach for it only when object identity is exactly what you need to track. Most code never needs it.

6.1 Real Use Cases

  • Serialization frameworks that must track which exact objects they’ve already written, to handle shared references and cycles.
  • Deep-copy routines that need to remember each original object and its clone.
  • Object graph traversals where two equal-looking nodes are still distinct and must be visited separately.
  • Attaching metadata to specific object instances, not to a class of equal values.

Notice the pattern. Each case cares about the physical object, not its contents. That’s when identity beats equality.

6.2 Why Frameworks Like It

Take a library that serializes an object graph. Two different nodes might hold the same data but still need separate handling. A normal map would merge them, which would break the graph.

IdentityHashMap keeps them apart. It also dodges any slow or buggy equals() and hashCode() on user classes. For framework internals, that safety is worth a lot.

6.3 Don’t Use It as a General Map

This is the big warning. Never reach for IdentityHashMap just because it’s a map. If you use it by accident, your keys will behave in ways that confuse everyone.

For normal lookups by value, always use HashMap. Keep IdentityHashMap for the narrow cases where identity truly matters.

7. EnumMap vs IdentityHashMap vs HashMap

Let’s put the three side by side. Each one keys its entries differently, and that drives when you’d pick it.

Feature HashMap EnumMap IdentityHashMap
Key type Any object Enum constants only Any object
Key comparison equals() Enum identity == reference
Hashing hashCode() ordinal() index identityHashCode()
Backing store Bucket array + nodes Plain value array Open-addressed array
Iteration order No order Enum order No order
Null keys One allowed Not allowed Allowed
Typical use Everyday lookups Enum-keyed tables Identity tracking

The takeaway is short. Use HashMap by default. Switch to EnumMap when keys are enums. Reach for IdentityHashMap only when reference identity is the point.

8. Common Mistakes and Pitfalls

Both maps come with a few traps. Let’s name them so you can steer clear.

8.1 Forgetting the Class in EnumMap

A new EnumMap needs the enum’s Class object. Write new EnumMap<>(Day.class), not new EnumMap<>(). Skip the class and the code won’t even compile.

8.2 Using IdentityHashMap Without Meaning To

This one bites hard. A developer picks IdentityHashMap thinking it’s a faster HashMap. Then keys that should match don’t, and the bug is a nightmare to trace.

So treat IdentityHashMap as a specialist. Reach for it on purpose, never by habit.

8.3 Expecting Value Equality From IdentityHashMap

Two equal strings, two equal Integers, two equal records. In an IdentityHashMap, each pair can land as two keys. If you assumed value equality, your counts and lookups go wrong.

A Note on Autoboxing

Boxed numbers make this worse. The same int can box into different Integer objects. Put them in an IdentityHashMap and you may get surprise duplicates. Stick to HashMap for numeric keys.

8.4 Assuming EnumMap Is Thread-Safe

It isn’t. Neither map is synchronized. For shared access across threads, add your own synchronization or pick a concurrent map.

9. Interview Questions

Q: What is an EnumMap and why use it?

A: An EnumMap is a Map whose keys must be constants of a single enum type. It stores values in a plain array indexed by each key’s ordinal, so it is faster and lighter than a HashMap for enum keys. It also keeps keys in enum declaration order.

Q: How does EnumMap store its data internally?

A: It holds a simple array of values, one slot per enum constant. A put writes to the slot at key.ordinal(), and a get reads from it. There is no hashing, no buckets, and no collision handling.

Q: Can an EnumMap have a null key?

A: No. A null key throws a NullPointerException. Values can be null, but keys cannot, since keys must be real enum constants.

Q: What makes IdentityHashMap different from a normal HashMap?

A: It compares keys using reference equality (==) instead of equals(), and it hashes with System.identityHashCode() instead of the key’s hashCode(). So two objects that are equal by value count as two separate keys.

Q: Give a real use case for IdentityHashMap.

A: Serialization and deep-copy code often uses it to track which exact objects have already been processed. Two nodes with equal data still need separate handling, so identity matters more than value equality there.

Q: Why is using IdentityHashMap as a general-purpose map dangerous?

A: Keys that look equal by value will not match, because the map only checks reference identity. Lookups miss, entries duplicate, and the bugs are hard to spot. Use it only when identity tracking is the actual goal.

Q: Does IdentityHashMap call your overridden equals() and hashCode()?

A: No. It ignores both. It uses == for comparison and System.identityHashCode() for hashing, so your custom equals() and hashCode() have no effect.

Q: Are EnumMap and IdentityHashMap thread-safe?

A: Neither is thread-safe. For concurrent access you must synchronize externally or choose a concurrent alternative.

10. Conclusion

Let’s wrap up. EnumMap and IdentityHashMap are both niche, but each solves a clear problem.

EnumMap is the go-to map when your keys are enum constants. It’s backed by a simple array, so it’s fast, small, and keeps keys in enum order. Any time you see enum keys, think EnumMap first.

IdentityHashMap is the specialist. It keys by reference identity, not value, which suits serializers, deep-copy tools, and object graph work. Reach for it only when identity is exactly what you need, and never as a plain HashMap replacement.

Together they round out the map family. You now know when the standard HashMap isn’t the best fit, and which special map to grab instead.

Further Reading

Leave a Comment