The Golden Rule of Java: Why You Must Override hashCode() When You Override equals()
In Java, the relationship between the equals() and hashCode() methods is a fundamental contract. Breaking it can lead to baffling bugs…
The Golden Rule of Java: Why You Must Override hashCode() When You Override equals()

In Java, the relationship between the equals() and hashCode() methods is a fundamental contract. Breaking it can lead to baffling bugs, especially when your objects are stored in collections like HashSet or HashMap. The rule is simple: if you override one, you must override the other. Let's explore why this isn't just a suggestion, but a necessity.
The Short Answer
When you place objects into a hash-based collection like a HashSet or HashMap, the collection uses hashCode() first as a quick filter to place the object into a "bucket." It only uses the more expensive equals() method to check for true equality against other objects within that same bucket.
- If you only override
equals(): Two objects you consider equal will returntruefromequals(), but they will likely have different hash codes from the defaultObjectclass implementation. TheHashSetwill place them in different buckets and never bother callingequals(), leading it to believe they are two distinct objects. - If you only override
hashCode(): Two objects might have the same hash code, placing them in the same bucket. However, the defaultequals()method (which just compares memory addresses) will returnfalse. The collection will correctly assume they are different objects that just happened to have a hash collision.
Both scenarios break the expected behavior of these collections. Therefore, you must override both methods to ensure your objects work correctly everywhere.
The Foundation: == vs. equals()
First, let’s clarify the basics.
The == operator is straightforward.
- For primitive types (like
int,char), it compares their values. - For reference types (objects like
Person), it compares their memory addresses. It checks if two references point to the exact same object in the heap.
// Primitive comparison
int a = 10;
int b = 10;
System.out.println(a == b); // true, because the values are the same
// Object comparison
Person p1 = new Person("Seven");
Person p2 = new Person("Seven");
System.out.println(p1 == p2); // false, they are two separate objects in memory
The equals() method, by default, does the same thing as == for objects. The source code in the Object class confirms this:
public boolean equals(Object obj) {
return (this == obj);
}
However, the purpose of equals() is to allow us to define our own idea of "logical equality." For example, a String class overrides equals() to compare the actual character sequences, not the memory locations. We must do the same for our own objects.
Understanding hashCode()
The hashCode() method returns an integer representation of an object. Its primary purpose is to enable high-performance searching in hash-based collections like HashMap, HashSet, and Hashtable.
Instead of iterating through every element to find a match (an O(n) operation), a hash collection uses the object’s hash code to jump directly to the bucket where the object should be. This turns the search into a nearly O(1) operation.
The Official Contract Between equals() and hashCode()
Java’s documentation specifies a strict contract:
- If two objects are equal according to the
equals(Object)method, then calling thehashCode()method on each of the two objects must produce the same integer result. - If two objects are unequal according to the
equals(Object)method, it is not required that callinghashCode()on each of them produce distinct results. However, producing distinct results may improve the performance of hash tables.
Let’s see what happens when we break this contract.
Scenario 1: Overriding equals() but Not hashCode()
This is the most common mistake. We define logical equality but forget to update the hash code to match.
Consider a simple Person class:
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
public class Person {
String name;
public Person(String name) {
this.name = name;
}
// We define equality based on the 'name' field
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Person person = (Person) o;
return Objects.equals(name, person.name);
}
public static void main(String[] args) {
Person p1 = new Person("Alex");
Person p2 = new Person("Alex");
System.out.println("p1.equals(p2): " + p1.equals(p2)); // true
System.out.println("p1.hashCode(): " + p1.hashCode()); // e.g., 12345678
System.out.println("p2.hashCode(): " + p2.hashCode()); // e.g., 87654321 (Different!)
Set<Person> set = new HashSet<>();
set.add(p1);
set.add(p2);
System.out.println("Set size: " + set.size()); // Outputs 2
}
}
Analysis:
p1.equals(p2)correctly returnstruebecause we defined equality based on the name.- However, since we didn’t override
hashCode(), each object gets its hash code from the defaultObjectclass implementation, which is typically based on the object's memory address. Sincep1andp2are different objects, they have different hash codes. - When we add
p1to theHashSet, it calculates its hash code (e.g., 12345678) and places it in a bucket. - When we add
p2, theHashSetcalculates its different hash code (e.g., 87654321) and places it in another bucket. - The
HashSetnever even callsp1.equals(p2)because they are in different buckets. It assumes they are different objects, and the set ends up with two elements.
This violates the first rule of the contract: equals() was true, but the hash codes were different.
The Fix: Overriding Both Methods
To fix this, we must provide a hashCode() implementation that is consistent with our equals() method. If equality is based on the name field, the hash code should be too.
public class Person {
String name;
// constructor and equals() method are the same...
// A consistent hashCode implementation
@Override
public int hashCode() {
return Objects.hash(name);
}
public static void main(String[] args) {
// ... same main method code ...
// Set<Person> set = new HashSet<>();
// set.add(p1);
// set.add(p2);
// System.out.println("Set size: " + set.size()); // Now outputs 1
}
}
With this change:
p1.hashCode()andp2.hashCode()will now return the same value because they are both calculated from the string "Alex".- When adding
p2to the set, it gets sent to the same bucket asp1. - Now, the
HashSetwill call theequals()method on the objects within that bucket to check for true duplicates. - Since
p1.equals(p2)istrue, the set recognizesp2as a duplicate and does not add it. The set's size correctly remains 1.
The contract is satisfied, and the collection behaves as expected.
Conclusion
While you can technically get by with only overriding equals() if you never use your object in a hash-based collection, this is a dangerous gamble. The moment someone else uses your class in a HashMap or HashSet, they will encounter strange and unpredictable behavior.
Adhering to the contract is essential for writing robust, predictable, and maintainable Java code. Remember the golden rule: When you override equals(), you must always override hashCode() as well.
Thank you for your patience in reading this article!
If you found this article helpful, please give it a clap 👏, and share it with friends in need and follow for more Spring Boot insights.
Your support is my biggest motivation to continue to output technical insights!
메타데이터
- post_id
- bb61a8cd793a
- slug
- the-golden-rule-of-java-why-you-must-override-hashcode-when-you-override-equals-bb61a8cd793a
- url
- https://medium.com/codeelevation/the-golden-rule-of-java-why-you-must-override-hashcode-when-you-override-equals-bb61a8cd793a
- canonical_url
- https://medium.com/codeelevation/the-golden-rule-of-java-why-you-must-override-hashcode-when-you-override-equals-bb61a8cd793a
- author_url
- https://medium.com/@umeshcapg
- status
- ok
- fetched_at
- 2026-06-28 04:42:08