Java equals and hashCode: Fix Broken HashMap and HashSet Lookups
Diagnose collection bugs caused by inconsistent equality, mutable keys, inheritance, and accidental identity comparisons.
On this page
The contract collections depend on
Hash-based collections use hashCode() to choose a bucket and equals() to determine whether two keys represent the same value. Equal objects must have equal hash codes, and equality should be reflexive, symmetric, transitive, and consistent while the compared state is unchanged. Unequal objects may share a hash; collisions are normal and do not imply the keys are the same.
When this contract is broken, a confusing symptom is that map.get(key) returns null even though a similar key was inserted. Before blaming a JVM optimization, inspect the class's equality and hash implementation and compare both values for the lookup and stored key.
Mutable keys create unreachable entries
Suppose a key's hash depends on email, and the application changes that email after inserting the object into a HashMap. The entry remains in the bucket selected by the old hash, while a later lookup uses the new hash and searches another bucket. The map may appear to have lost the entry even though it is still present during iteration.
Use immutable value objects as keys. If key state must change, remove the entry before mutating equality-relevant fields, then insert it again. Avoid mutable ORM entities as hash keys when their generated ID is assigned only after persistence unless the equality strategy explicitly accounts for that lifecycle.
Inheritance and generated implementations
Equality across a class hierarchy can violate symmetry: a base-class instance may consider a subclass equal while the subclass rejects the base instance because of extra fields. Prefer final value types, records, or a deliberate equality policy when inheritance is unavoidable. Generated equals methods are useful, but verify which fields they include and whether those fields remain stable.
Debugging checklist
- Reproduce with the exact key instance and a logically equal new instance.
- Inspect
hashCode(), equality in both directions, and the fields they depend on. - Check whether a key changed after insertion or is represented by different proxy/entity classes.
- Add a unit test asserting equal values have equal hashes and map lookup succeeds after reconstruction.
- Make key state immutable and keep identity rules consistent with the domain.
Increasing map capacity cannot repair an equality contract or mutable-key defect.