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.
🟢 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:
key.hashCode() = 100→put(key, "Value")→ bucket #100key.setId(2)→hashCode() = 200get(key)→ searches in bucket #200 →nullremove(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
- Using JPA Entity as a key — Entity changes during operation
- StringBuilder as a key — mutable: its content can be changed after adding to Map, breaking hashCode. String guarantees immutability.
- Collections as keys —
ArrayListis 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
- Concurrent modification: One thread modified the key, another searches — race condition
- Serialization: On deserialization, a new object is created — the old “ghost” remains
- 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
- Always
finalfields in keys:private final String id; - Records — compiler guarantees immutability
- Defensive copy when receiving a key in the constructor
- 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]]