00:00

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 == in containsValue() and remove(key, value).
  • Hash codes from System.identityHashCode(), not from the key's hashCode().
  • One null key and null values allowed.
  • No guaranteed order; O(1) average operations.
  • Not synchronized.

2. Class Hierarchy and Declaration

IdentityHashMap class hierarchy in Java
java.util.IdentityHashMap
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:

IdentityVsHashMap.java
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"));
    }
}
Output
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

ConstructorWhat 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:

VisitedObjects.java
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));
    }
}
Output
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:

IntegerCacheIdentity.java
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
    }
}
Output
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:

MethodIdentity 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

FeatureIdentityHashMapHashMapWeakHashMap
Key comparison==equals()equals()
Hash codeSystem.identityHashCode()key.hashCode()key.hashCode()
Key referencesStrongStrongWeak
StorageFlat array, linear probingBuckets with lists/treesBuckets with lists
Follows Map contractNo (on purpose)YesYes

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 with System.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