JavaOOPbeginner
Updated:

The equals and hashCode Contract in Java Explained

6 min read

Learn why overriding equals without hashCode breaks your objects in HashMaps and HashSets, and how to implement both correctly so equal objects behave as equal.

TL;DR – Quick Answer

equals defines when two objects are logically equal, and hashCode returns an int used by hash-based collections to bucket objects. The contract requires that if two objects are equal, they must return the same hashCode. If you override equals you must override hashCode, or your objects will misbehave in HashMap and HashSet, appearing as duplicates or going missing.

On This Page

equals and hashCode are the two methods every Java developer overrides eventually and half of them override wrong. Get them right and your objects behave predictably as map keys and set members; get them wrong and objects vanish from HashSets, duplicate silently, or become impossible to look up. This is one of the most common real-world bugs and one of the most common interview questions.

This tutorial explains why the two methods are joined at the hip, walks through correct implementations, and shows exactly how a HashMap uses them. It builds on how HashMap works internally, so keep that open as a companion.

What each method means

Both methods live on java.lang.Object, so every class you write inherits them.

  • equals(Object o) answers "are these two objects logically the same?"
  • hashCode() returns an int that hash-based collections use to decide which bucket an object goes into.

The default equals inherited from Object compares identity — it returns true only if both references point to the exact same object. The default hashCode is derived from the object's memory address. For value-like classes (a Point, a Money, an Employee with an id) that default is almost never what you want.

class Point {
    int x, y;
    Point(int x, int y) { this.x = x; this.y = y; }
}

public class Main {
    public static void main(String[] args) {
        Point a = new Point(1, 2);
        Point b = new Point(1, 2);
        System.out.println(a.equals(b)); // false! — default identity comparison
    }
}

a and b have identical coordinates but the default equals says they're different, because they're two separate objects in memory. To make them equal by value, we override equals.

Overriding equals correctly

A correct equals follows a standard shape: check identity, check type, then compare the fields that define equality.

class Point {
    int x, y;
    Point(int x, int y) { this.x = x; this.y = y; }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;              // same object
        if (o == null || getClass() != o.getClass()) return false; // type check
        Point other = (Point) o;                 // safe cast
        return x == other.x && y == other.y;     // field comparison
    }
}

Now new Point(1, 2).equals(new Point(1, 2)) returns true. The equals contract requires four properties, and interviewers do ask you to name them:

  • Reflexive: x.equals(x) is always true.
  • Symmetric: if x.equals(y) then y.equals(x).
  • Transitive: if x.equals(y) and y.equals(z) then x.equals(z).
  • Consistent: repeated calls return the same result if the objects don't change.

Plus, x.equals(null) must always be false — which our null check guarantees.

Why hashCode must come along

Here's the crux. If you override equals but not hashCode, you've created objects that are "equal" but return different hash codes — and that quietly breaks every hash-based collection.

import java.util.HashSet;

public class Broken {
    public static void main(String[] args) {
        HashSet<Point> set = new HashSet<>();
        set.add(new Point(1, 2));
        System.out.println(set.contains(new Point(1, 2)));
        // prints FALSE even though equals says they're equal!
    }
}

Why does contains fail? A HashSet first calls hashCode to find the right bucket, then uses equals only on objects in that bucket. Since the two Points have different default hash codes, they map to different buckets, and equals is never even called. The object is effectively lost.

Common mistake: Overriding equals and forgetting hashCode is the number-one hash-collection bug. Your object goes into a HashMap or HashSet fine but can never be found again, or duplicates slip in. If you override one, override both — always.

The contract, stated precisely

The relationship between the two methods is a formal contract:

  1. If two objects are equal (equals returns true), they MUST return the same hashCode. This is the rule that breaks lookups when violated.
  2. If two objects have the same hashCode, they are NOT required to be equal. Two different objects sharing a hash code is a collision — legal and expected.

Read rule 2 carefully: hashCode is allowed to be imperfect. A hash code just narrows the search to a bucket; equals does the final, exact comparison. Collisions only cost a little performance, never correctness — the full mechanics are in how HashMap works internally.

Implementing hashCode correctly

The modern, readable way uses Objects.hash, which combines your fields into one hash code. Use the same fields you compared in equals.

import java.util.Objects;

class Point {
    int x, y;
    Point(int x, int y) { this.x = x; this.y = y; }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Point other = (Point) o;
        return x == other.x && y == other.y;
    }

    @Override
    public int hashCode() {
        return Objects.hash(x, y);   // same fields as equals
    }
}

Now the earlier HashSet.contains(new Point(1, 2)) returns true, because both Points hash to the same bucket and equals confirms they match. Objects.equals(a, b) is also handy inside equals for comparing object-typed fields safely, because it handles null for you.

Pro tip: The golden rule is "equals and hashCode must use the same fields." If equals compares x and y, hashCode must be built from x and y. Using different fields for each is a subtle way to violate the contract even when both methods exist.

How HashMap uses them together

Putting it end to end, when you do map.put(key, value):

  1. Java calls key.hashCode() to compute the bucket index.
  2. If that bucket already holds entries, it walks them and calls equals to check for a matching key.
  3. A match updates the existing value; no match adds a new entry.

map.get(key) does the same two-step: hash to the bucket, then equals within it. This is why a proper pair of methods is mandatory for any object you use as a HashMap key or a HashSet element. Mutable keys are dangerous for the same reason — if you change a field after insertion, the object's hash code changes and it's stranded in the wrong bucket.

Interview note: "What happens if you use a mutable object as a HashMap key and then mutate it?" The answer: its hashCode changes, so the map looks in the wrong bucket and can no longer find the entry — it's effectively lost. This is why immutable keys like String and Integer are ideal. Expect this and the "override equals only" trap on the Java collections interview questions.

A quick checklist

Before you ship a class used in collections, confirm:

  • Overrode equals? Then override hashCode.
  • Both use the same set of fields.
  • equals is reflexive, symmetric, transitive, consistent, and false for null.
  • Keys used in maps/sets are ideally immutable so their hash code never changes.

Modern IDEs generate a correct pair for you, and records (Java 16+) generate them automatically from the components — but you must understand the contract to trust the generated code and to explain it in an interview.

Where to go next

This contract is the bridge between core OOP and the collections framework, so review the four OOP concepts if identity vs equality still feels abstract, then dive into how HashMap works internally to see the buckets and collisions in action.

Implementing and debugging these methods on real domain objects — and explaining them under interview pressure — is exactly the kind of practice built into the collections module of the Java Full Stack course, with the full sequence mapped in the Java learning hub.

Frequently Asked Questions

Why must you override hashCode when you override equals?
Hash-based collections like HashMap and HashSet first use hashCode to find the right bucket, then use equals to compare within it. If two logically equal objects return different hash codes, they land in different buckets and the collection never even calls equals, so it treats them as different. Overriding only equals silently breaks lookups and duplicate detection.
What is the contract between equals and hashCode?
The contract has two key rules. First, if two objects are equal according to equals, they must return the same hashCode. Second, if two objects have the same hashCode they are not required to be equal, since hash collisions are allowed. equals must also be reflexive, symmetric, transitive, and consistent.
What happens if two objects have the same hashCode but are not equal?
That is a hash collision, and it is perfectly legal. The collection places both objects in the same bucket and uses equals to tell them apart within that bucket. Collisions only affect performance, not correctness, as long as equals is implemented properly. Good hashCode implementations spread objects across buckets to keep collisions rare.
Can I use Objects.equals and Objects.hash to implement them?
Yes, and it is the recommended modern approach. Objects.equals handles null safely when comparing fields, and Objects.hash generates a combined hash code from the fields you pass. Together they let you write correct, readable implementations in a few lines, though for very hot code paths a hand-written hashCode can be faster.
Does the default equals from Object compare field values?
No. The default equals inherited from Object compares object identity, meaning it returns true only when both references point to the exact same object in memory. To compare by field values, such as two Point objects with the same coordinates, you must override equals yourself and provide a matching hashCode.

Want to Build Your Career in Java Full Stack with AI?

Join CodeBegun and train with working industry engineers — Explore the Program

Apply for Demo Class →
Siva Prasad Galaba
Founder, CodeBegun · Staff Engineer

Founder of CodeBegun. 15+ years building Java systems at companies like Crunchyroll. Teaches Java, Spring Boot and system design the way the industry actually works, and mentors students through projects, mock interviews and placement preparation.

Technically reviewed by CodeBegun Technical TeamLast reviewed 15 July 2026 LinkedIn
Chat with us