Question 12 · Section 10

Can You Use a Mutable Object as a Key in HashMap?

Technically — yes, you can, but it's not recommended. If you modify the fields of a key object after adding it to HashMap, you won't be able to find that element.

Language versions: English Russian Ukrainian

🟢 Junior Level

Technically — yes, you can, but it’s not recommended. If you modify the fields of a key object after adding it to HashMap, you won’t be able to find that element.

Simple analogy: You put a letter in mailbox #5. Then you repaint the mailbox a different color and change the number to #10. The mail carrier will search by the new number and won’t find the letter.

Problem example:

public class MutableKey {
    private int id; // Not final! Can be changed

    public void setId(int id) { this.id = id; }

    @Override public int hashCode() { return id; }
    @Override public boolean equals(Object o) { /* by id */ }
}

MutableKey key = new MutableKey(1);
map.put(key, "Value"); // Landed in bucket #1
key.setId(2);          // Hash is now = 2
System.out.println(map.get(key)); // null! Key is "broken"

Solution: Use immutable objects as keys — String, Integer, Record.

🟡 Middle Level

The “Broken Key” Problem

HashMap “remembers” the object’s position by its hashCode() at the time of put(). If the hash changes — the element stays in the old bucket, but the search goes to the new one.

Failure mechanism:

  1. key.hashCode() = 100put(key, "Value") → bucket #100
  2. key.setId(2)hashCode() = 200
  3. get(key) → searches in bucket #200 → null
  4. remove(key) → also won’t find → memory leak

Can You Modify Fields Not in hashCode/equals?

Technically yes, but it’s bad practice:

  • Misleads other developers
  • Risk: those fields might be added to the contract later

How to Do It Right

Approach Example
Immutable classes String, Integer, LocalDate
Record (Java 14+) public record Key(String id) {}
Final fields private final String id;
Delete-Update-Insert Remove → modify → add back

Common Mistakes

  1. Using JPA Entity as a key — Entity changes during operation
  2. StringBuilder as a key — mutable: its content can be changed after adding to Map, breaking hashCode. String guarantees immutability.
  3. Collections as keysArrayList is mutable, use a wrapper

🔴 Senior Level

Memory Leak Pattern

Map<MutableKey, LargeObject> cache = new HashMap<>();
// Keys are modified and "lost" — can't remove, can't find
// Old Gen fills → Full GC → OutOfMemoryError

“Ghost” entries occupy memory but are unreachable. In high-load systems, this leads to degradation over hours/days.

IdentityHashMap as a Workaround

Map<MutableKey, Value> map = new IdentityHashMap<>();
// Uses System.identityHashCode() and == instead of equals/hashCode
// Object's address doesn't change when fields are modified

⚠️ But this changes semantics: two different objects with the same fields = different keys. Do NOT use IdentityHashMap if you need content-based key comparison (two different objects with the same fields should be considered the same key). This solution is only for cases where reference identity matters.

Internal Mechanics

HashMap doesn’t track key changes. It:

  • Doesn’t copy the key (stores a reference)
  • Doesn’t validate hash on get/put
  • Fully trusts the key’s current hashCode()

Edge Cases

  1. Concurrent modification: One thread modified the key, another searches — race condition
  2. Serialization: On deserialization, a new object is created — the old “ghost” remains
  3. WeakHashMap: If the value references the key — cyclic dependency, leak

Production Diagnostics

Signs of mutable-key problems:

  • Growing Map size not matching business logic
  • get() returns null for “definitely existing” keys
  • Iterator finds elements that are unreachable via get()

Best Practices for Highload

  1. Always final fields in keys: private final String id;
  2. Records — compiler guarantees immutability
  3. Defensive copy when receiving a key in the constructor
  4. Static analysis — forbid mutable keys via code review

🎯 Interview Cheat Sheet

Must know:

  • Technically possible, but NOT recommended: modifying key fields = “broken key”
  • HashMap remembers bucket by hashCode() at put; on modification — searches elsewhere
  • get() = null, remove() won’t find → ghost entry, memory leak
  • IdentityHashMap as workaround (uses == and identityHashCode), but changes semantics
  • Best keys: String, Integer, Record (immutable by design)
  • StringBuilder/ArrayList are mutable — don’t use as keys

Common follow-up questions:

  • Can you modify fields not in hashCode? — technically yes, but bad practice
  • How to fix a mutable key? — remove → modify → insert back, or recreate the Map
  • Why is JPA Entity a bad key? — Entity changes on persist/merge
  • What is the Ghost Entry Pattern? — entries occupy memory but are unreachable via get

Red flags (DO NOT say):

  • “You can use a mutable object if you don’t change it” — fragile decision
  • “IdentityHashMap solves all problems” — no, it changes semantics to identity-based
  • “HashMap tracks key changes” — no, it doesn’t

Related topics:

  • [[13. What Happens If You Modify a Key After Adding It to HashMap]]
  • [[14. What Are the Requirements for a HashMap Key]]
  • [[15. Why String Is Often Used as a Key in HashMap]]