Understanding equals() and hashCode() in Java: Why Your HashMap Might Be Misbehaving
Why your Java HashMap or HashSet may not work as expected and how equals() and hashCode() can silently break your collections.
Understanding equals() and hashCode() in Java: Why Your HashMap Might Be Misbehaving

If you’ve ever wondered why your HashMap refuses to find an object you know you just put in there, or why your HashSet suddenly contains what looks like duplicate elements, you’ve likely violated the sacred contract between equals() and hashCode().
Let’s fix that once and for all.
Why equals() and hashCode() Matter
In Java, objects are stored and retrieved in hash-based collections like HashMap, HashSet, using a two-step process:
hachCode(): determines which “bucket” the object goes into.equals(): used to compare objects within the same bucket.
If either one doesn’t behave as expected, you’ll end up with:
- Missing elements,
- Duplicates where there shouldn’t be any,
- Or lookups that fail even though the element “exists”.
The Contract Between equals() and hashCode()
Java defines a strict contract between these two methods:
- If two objects are equal according to
equals(), they must have the samehashCode(). - If two objects have the same
hashCode(), they are not necessarily equal. - If
equals()is overridden, you must also overridehashCode()(and vice versa). - The
hashCode()value should be consistent, calling it multiple times on the same object (if unchanged) must return the same number.
Let’s see why this matters.
A Broken Example
Here’s a simple class that breaks the rules:
public class Person {
private String name;
private int age;
// Constructor
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// Overriding equals() only
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Person)) return false;
Person person = (Person) o;
return age == person.age && name.equals(person.name);
}
// hashCode() NOT overridden!
}
Now let’s test it:
Person p1 = new Person("Alice", 25);
Person p2 = new Person("Alice", 25);
System.out.println(p1.equals(p2)); // true
HashSet<Person> set = new HashSet<>();
set.add(p1);
System.out.println(set.contains(p2)); // false
Even though p1.equals(p2) returns true, the HashSet fails to find p2.
Why? Because their hashCode() values (inherited from object) are different, so they end up in different buckets.
The Correct Implementation
To fix, we must override both methods together:
import java.util.Objects;
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Person)) return false;
Person person = (Person) o;
return age == person.age && Objects.equals(name, person.name);
}
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
Now:
Person p1 = new Person("Alice", 25);
Person p2 = new Person("Alice", 25);
System.out.println(p1.equals(p2)); // true
System.out.println(p1.hashCode() == p2.hashCode()); // true
HashSet<Person> set = new HashSet<>();
set.add(p1);
System.out.println(set.contains(p2)); // true
Everything works as intended.
Why HashMap and HashSet Depend on These Methods
Let’s peek under the hood.
**HashSet**
Internally, HashSet uses a HashMap, it stores your elements as keys.
When you do:
set.add(p1);
It calls hashCode() to find the correct bucket and then equals() to check for duplicates.
If your hashCode() and equals() are inconsistent, the HashSet might store duplicates in different buckets.
HashMap
A HashMap uses keys hashCode() to find the right bucket, and equals() to check if the key already exists.
For example:
Map<Person, String> map = new HashMap<>();
map.put(p1, "Developer");
System.out.println(map.get(p2)); // null
Without a proper hashCode(), it looks in the wrong place.
Once fixed, it works:
System.out.println(map.get(p2)); // "Developer"
A Word on Object.hash() and Performance
Using Objects.hash() is simple and safe, but it creates an internal array, which adds a tiny overhead.
If performance is critical, you can implement our own lightweight hash:
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
The multiplier 31 is a common choice in Java (it’s an odd prime number that produces fewer collisions).
Common Pitfalls
- Mutable fields in hash-based collections
set.add(p1);
p1.setName("Bob"); // modifying key fields
set.contains(p1); // false now!
Always use immutable fields (or avoid mutating them after insertion).
- Relying on default implementations: The default
equals()inObjectchecks for reference equality, not content equality, meaning onlya == breturns true. - Forgetting symmetry or transitivity in
equals(): Symmetry:a.equals(b)=>b.equals(a), transitivity: ifa.equals(b)andb.equals(c)=>a.equals(c).
Wrap up
The equals() and hashCode() contract is one of those “simple-sounding but critical” foundations of Java.
Break it, and your collections will quietly betray you.
Respect it, and your HashMap and HashSet will behave like loyal data structures they were meant to be.
If you found this helpful, consider following me for more clear-headed guides on Java and Spring Boot.
메타데이터
- post_id
- a97b87bb4e8b
- slug
- understanding-equals-and-hashcode-in-java-why-your-hashmap-might-be-misbehaving-a97b87bb4e8b
- url
- https://medium.com/@ayoubtaouam/understanding-equals-and-hashcode-in-java-why-your-hashmap-might-be-misbehaving-a97b87bb4e8b
- canonical_url
- https://medium.com/@ayoubtaouam/understanding-equals-and-hashcode-in-java-why-your-hashmap-might-be-misbehaving-a97b87bb4e8b
- author_url
- https://medium.com/@ayoubtaouam
- status
- ok
- fetched_at
- 2026-06-28 04:42:08