IdentityHashMap in Java – Guide with Examples
IdentityHashMap is a special-purpose Map that compares keys by reference (==) instead of by value (equals()). Two keys are the same only if they are literally the same object in memory.
It lives in java.util, was added in JDK 1.4, and intentionally breaks the general Map contract. That sounds strange, but it is exactly what tools like serializers, deep-copy utilities and graph algorithms need. This tutorial shows when and how to use it.
1. What Is an IdentityHashMap?
Imagine two identical-looking printed tickets. A normal map (HashMap) says "same text, same ticket". An IdentityHashMap says "two pieces of paper, two tickets". It cares about which object, not what it contains.
- Keys compared with
==; values also compared with==incontainsValue()andremove(key, value). - Hash codes from
System.identityHashCode(), not from the key'shashCode(). - One
nullkey andnullvalues allowed. - No guaranteed order; O(1) average operations.
- Not synchronized.
2. Class Hierarchy and Declaration
public class IdentityHashMap<K, V> extends AbstractMap<K, V>
implements Map<K, V>, java.io.Serializable, Cloneable
3. IdentityHashMap vs HashMap
Two String objects with the same text are equal but not identical. HashMap treats them as one key; IdentityHashMap treats them as two:
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Map;
public class IdentityVsHashMap {
public static void main(String[] args) {
String k1 = new String("id");
String k2 = new String("id"); // equal content, different object
System.out.println("k1.equals(k2): " + k1.equals(k2));
System.out.println("k1 == k2: " + (k1 == k2));
Map<String, Integer> hash = new HashMap<>();
hash.put(k1, 1);
hash.put(k2, 2); // equals() -> same key, value replaced
Map<String, Integer> identity = new IdentityHashMap<>();
identity.put(k1, 1);
identity.put(k2, 2); // == -> different keys
System.out.println("HashMap size: " + hash.size() + " " + hash);
System.out.println("IdentityHashMap size: " + identity.size());
System.out.println("identity.get(k1)=" + identity.get(k1) + ", get(\"id\" literal)=" + identity.get("id"));
}
}
k1.equals(k2): true
k1 == k2: false
HashMap size: 1 {id=2}
IdentityHashMap size: 2
identity.get(k1)=1, get("id" literal)=null
Looking up with the literal "id" returns null, because the literal is yet another object.
4. Constructors
| Constructor | What it does |
|---|---|
IdentityHashMap() | Empty map sized for about 21 entries. |
IdentityHashMap(int expectedMaxSize) | Empty map sized for the expected number of entries. |
IdentityHashMap(Map<? extends K, ? extends V> m) | A map with the entries of m. |
5. Main Use Case: Tracking Objects in a Graph
When walking an object graph, for example to serialize, deep-copy or detect cycles, you must remember which objects you have already visited. Two different objects that happen to be equal must still be visited separately, and objects with a broken or expensive equals() must not confuse the bookkeeping. An identity-based set solves both problems:
import java.util.ArrayList;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Objects;
import java.util.Set;
public class VisitedObjects {
// Two different nodes can be "equal" by value, but we must track each OBJECT once
static class Node {
final String name;
final List<Node> links = new ArrayList<>();
Node(String name) { this.name = name; }
@Override public boolean equals(Object o) { return o instanceof Node n && n.name.equals(name); }
@Override public int hashCode() { return Objects.hash(name); }
}
static int countReachable(Node start) {
Set<Node> visited = Collections.newSetFromMap(new IdentityHashMap<>());
List<Node> todo = new ArrayList<>(List.of(start));
while (!todo.isEmpty()) {
Node n = todo.remove(todo.size() - 1);
if (visited.add(n)) todo.addAll(n.links); // add() is false for an object already seen
}
return visited.size();
}
public static void main(String[] args) {
Node a = new Node("A"), b = new Node("B"), b2 = new Node("B"); // b and b2 are equal but distinct
a.links.add(b);
a.links.add(b2);
b.links.add(a); // a cycle: A -> B -> A
System.out.println("Distinct node objects reachable from A: " + countReachable(a));
}
}
Distinct node objects reachable from A: 3
With a HashSet, b and b2 would count as one node and the answer would be 2. Collections.newSetFromMap(new IdentityHashMap<>()) is the standard way to build an identity set.
Other uses: proxies and frameworks that keep per-object data, topology-preserving copies, and caches keyed by objects whose equals() cannot be trusted.
6. Pitfalls with Boxed Values and Strings
Because comparison uses ==, results depend on whether Java reuses the same object. Autoboxing caches Integer values from −128 to 127, but creates new objects for larger values:
import java.util.IdentityHashMap;
import java.util.Map;
public class IntegerCacheIdentity {
public static void main(String[] args) {
Map<Integer, String> map = new IdentityHashMap<>();
map.put(100, "small"); // autoboxed with Integer.valueOf(100) -> cached object
map.put(1000, "large"); // Integer.valueOf(1000) -> a new object each time
System.out.println("get(100): " + map.get(100)); // same cached object -> found
System.out.println("get(1000): " + map.get(1000)); // different object -> not found
}
}
get(100): small get(1000): null
- Do not use IdentityHashMap with boxed numbers or strings unless you control exactly which objects are used.
- String literals with the same text are the same object (interned), but strings built at runtime usually are not.
- IdentityHashMap is not a general-purpose map; the documentation says so explicitly.
7. How IdentityHashMap Works Internally
Unlike HashMap, IdentityHashMap does not use buckets or entry objects. It stores keys and values side by side in one flat array: [key0, value0, key1, value1, …].
- The slot is chosen from
System.identityHashCode(key). - Collisions are resolved with linear probing: if a slot is taken, the next pair of slots is tried.
- The table resizes when it is more than about two-thirds full.
- No entry objects are created, so it can be faster and use less memory than HashMap for its purpose.
8. Methods
IdentityHashMap has the normal Map methods, with identity semantics:
| Method | Identity behaviour |
|---|---|
put(k, v), get(k), containsKey(k), remove(k) | Match only the same key object (==). |
containsValue(v) | Matches only the same value object. |
remove(k, v), replace(k, old, new) | Compare both key and value with == (fixed in Java 20). |
keySet(), entrySet() | Views whose contains() and remove() also use identity. |
equals(o), hashCode() | Based on identity of keys and values. |
clone() | Shallow copy. |
9. IdentityHashMap vs HashMap vs WeakHashMap
| Feature | IdentityHashMap | HashMap | WeakHashMap |
|---|---|---|---|
| Key comparison | == | equals() | equals() |
| Hash code | System.identityHashCode() | key.hashCode() | key.hashCode() |
| Key references | Strong | Strong | Weak |
| Storage | Flat array, linear probing | Buckets with lists/trees | Buckets with lists |
| Follows Map contract | No (on purpose) | Yes | Yes |
10. Thread Safety
IdentityHashMap is not synchronized. Use Collections.synchronizedMap(new IdentityHashMap<>()) if several threads share it.
11. Summary
- IdentityHashMap compares keys (and values) with
==and hashes them withSystem.identityHashCode(). - It deliberately breaks the Map contract and is meant for special cases.
- The main use is tracking objects in graph algorithms, serialization and deep copies.
- It stores keys and values in one array with linear probing.
- Be careful with strings and boxed numbers, whose identity depends on caching.
Related Tutorials
Reference: Java SE 21 API: java.util.IdentityHashMap
Frequently Asked Questions (FAQ)
Q1. What is an IdentityHashMap in Java?
IdentityHashMap is a Map in java.util that treats two keys as equal only if they are the same object (k1 == k2), instead of using equals(). It uses System.identityHashCode() for hashing.
Q2. What is the difference between IdentityHashMap and HashMap?
HashMap uses equals() and hashCode(), so equal objects are the same key. IdentityHashMap uses == and the identity hash code, so only the very same object is the same key. HashMap uses buckets; IdentityHashMap uses one array with linear probing.
Q3. Why does IdentityHashMap violate the Map contract?
The Map interface says keys are compared with equals(). IdentityHashMap intentionally uses reference equality instead, because some algorithms need to distinguish objects that are equal by value. The JavaDoc clearly marks it as a special-purpose class.
Q4. When should I use IdentityHashMap?
When you must track specific objects regardless of their equals(): object-graph traversal, serialization, deep copying, cycle detection, and frameworks that attach data to proxies or other objects whose equals() may be unreliable.
Q5. What is System.identityHashCode()?
A method that returns the hash code the default Object.hashCode() would return, even if the class overrides hashCode(). It depends on the object's identity, not its content.
Q6. Does IdentityHashMap allow null keys and values?
Yes, one null key and any number of null values.
Q7. Why does get() return null for a String key that I put earlier?
You looked up with a different String object that has the same text. In an IdentityHashMap, only the exact object you used in put() finds the entry.
Q8. How do I create an identity-based Set?
Set<Object> seen = Collections.newSetFromMap(new IdentityHashMap<>());
Q9. How does IdentityHashMap store its data?
In a single array where each key is followed by its value. Collisions are handled with linear probing (trying the next slots), so no separate entry objects are created.
Q10. Is IdentityHashMap faster than HashMap?
For its use case it can be, because == is cheaper than equals(), identity hash codes are cached by the JVM, and there are no entry objects. But speed is not a reason to use it; choose it only when you need identity semantics.
Q11. Is IdentityHashMap thread-safe?
No. Wrap it with Collections.synchronizedMap() when it is shared between threads.
Q12. Why do Integer keys sometimes work and sometimes not in an IdentityHashMap?
Autoboxing reuses cached Integer objects for values from −128 to 127, so map.get(100) finds the same object. For larger values a new object is created each time, so the lookup fails.
Q13. Does IdentityHashMap maintain insertion order?
No. Its iteration order depends on identity hash codes and is unpredictable.
Q14. What is the default capacity of IdentityHashMap?
The default constructor sizes the map for an expected maximum of 21 entries. The internal array grows when the map becomes more than about two-thirds full.
Q15. How does containsValue() work in IdentityHashMap?
It also uses ==, so it returns true only if the exact value object is stored in the map.
Q16. Can I use a record as a key in IdentityHashMap?
Yes, but the record's generated equals() and hashCode() are ignored. Two records with the same components are still different keys unless they are the same instance.
Q17. What is the difference between IdentityHashMap and WeakHashMap?
IdentityHashMap compares keys by identity and holds them strongly. WeakHashMap compares keys with equals() and holds them weakly, so unused keys can be garbage-collected.
Q18. Which JDK features use identity-based maps internally?
Java object serialization keeps track of already-written objects by identity so it can handle shared references and cycles, and many frameworks use identity maps to attach data to proxies and other objects.
Q19. When was IdentityHashMap introduced?
In JDK 1.4, together with LinkedHashMap and LinkedHashSet.
Q20. Should I use IdentityHashMap for String keys?
Almost never. Strings are value objects; users expect "abc" to find "abc". Use HashMap for string keys unless you deliberately want to track specific String instances.