← Back to list

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…

Umesh Kumar Yadav in CodeElevation · 2025-07-08 16:41 · 0 claps · 4.6 min read paywalled
#java #spring-boot #equals #hashcode
Open on Medium ↗
Wiki topics: 💑 · Relationships

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 return true from equals(), but they will likely have different hash codes from the default Object class implementation. The HashSet will place them in different buckets and never bother calling equals(), 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 default equals() method (which just compares memory addresses) will return false. 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 the hashCode() 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 calling hashCode() 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 returns true because we defined equality based on the name.
  • However, since we didn’t override hashCode(), each object gets its hash code from the default Object class implementation, which is typically based on the object's memory address. Since p1 and p2 are different objects, they have different hash codes.
  • When we add p1 to the HashSet, it calculates its hash code (e.g., 12345678) and places it in a bucket.
  • When we add p2, the HashSet calculates its different hash code (e.g., 87654321) and places it in another bucket.
  • The HashSet never even calls p1.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() and p2.hashCode() will now return the same value because they are both calculated from the string "Alex".
  • When adding p2 to the set, it gets sent to the same bucket as p1.
  • Now, the HashSet will call the equals() method on the objects within that bucket to check for true duplicates.
  • Since p1.equals(p2) is true, the set recognizes p2 as 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