Why Overriding equals Requires Overriding hashCode: An Interview‑Ready Guide

The article explains the contract between equals and hashCode in Java, shows what goes wrong when only one is overridden, demonstrates the correct implementation with Objects.equals and Objects.hash, covers IDE/Lombok generation, JPA entity nuances, inheritance pitfalls, records, and common anti‑patterns.

CodeNotes
CodeNotes
CodeNotes
Why Overriding equals Requires Overriding hashCode: An Interview‑Ready Guide

1. What the default equals does

Object.equals simply checks reference equality ( return this == obj;), so two distinct instances with identical fields are not considered equal.

public boolean equals(Object obj) {
    return (this == obj);
}

Therefore we usually override equals to define business‑level equality, e.g. comparing the id field of a User object.

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof User u)) return false;
    return Objects.equals(id, u.id);
}

2. Why hashCode must be overridden together

The JDK contract states that if two objects are equal according to equals, they must return the same hashCode. Violating this breaks collections that rely on hashing.

Example with only equals overridden:

User u1 = new User(1L, "Tom");
User u2 = new User(1L, "Tom");
Set<User> set = new HashSet<>();
set.add(u1);
set.contains(u2); // expected true, but returns false

HashSet/HashMap first locate a bucket using hashCode; different hash codes cause the collection to treat the objects as unrelated.

3. Standard way to override both

Pick the fields that represent the business identity (usually id) and use Objects.equals and Objects.hash:

@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof User u)) return false;
    return Objects.equals(id, u.id);
}

@Override
public int hashCode() {
    return Objects.hash(id);
}

Rule of thumb: the fields used in equals must be the same fields used in hashCode.

4. IDE / Lombok one‑click generation

IDEA can generate both methods (Cmd/Ctrl + N → equals() and hashCode()). Lombok annotations also help:

@Data                     // generates equals/hashCode for all fields
public class User { ... }

@EqualsAndHashCode(of = "id")
public class User { ... }

@EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class User {
    @EqualsAndHashCode.Include
    private Long id;
    private String name;
}

While Lombok is convenient, developers must understand the underlying rules to avoid misuse.

5. Special considerations for JPA entities

Using @Data on a JPA entity can cause problems:

All fields participate in equals/hashCode, triggering lazy‑loaded associations and causing performance issues.

Bidirectional relationships may lead to infinite recursion and StackOverflowError.

Auto‑generated IDs are null before persistence, making equals behave unexpectedly.

Recommended approach:

@Entity
@EqualsAndHashCode(of = "id")
public class User {
    @Id
    private Long id;
    // other fields
}

Only compare id after it is persisted; otherwise, two transient objects with id == null would be considered equal.

6. Inheritance and symmetry

When a subclass overrides equals using instanceof, symmetry can break:

public class A { int x; }
public class B extends A { int y; }
A a = new A(); a.x = 1;
B b = new B(); b.x = 1; b.y = 2;

a.equals(b); // may return true
b.equals(a); // returns false

This violates the contract. Solutions include making classes final or using getClass() == o.getClass() for strict type equality (at the cost of Liskov substitution).

7. Records (Java 14+)

Records automatically generate equals and hashCode based on all components, satisfying the contract and being immutable: public record Point(int x, int y) {} For simple data carriers, records are preferred; manual equals implementations are largely unnecessary.

8. Common anti‑patterns

Only overriding equals : HashSet/HashMap behave incorrectly.

hashCode returning a constant : All entries land in the same bucket, degrading performance to O(n) or O(log n) after treeification.

Throwing exceptions in equals : Must check type with instanceof before casting.

Using mutable fields in hashCode : Changing a field after insertion changes the hash, causing look‑ups to fail.

9. Summary

Always override hashCode when you override equals.

Use Objects.equals and Objects.hash; IDEs and Lombok can generate them.

Select business‑identity fields (typically id) for both methods.

For JPA entities, limit equality to the primary key and handle id == null cases.

Prefer records for immutable data carriers.

Keep fields used in hashCode immutable, or avoid mutating objects after they are placed in hash‑based collections.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

JavaBest PracticeshashmapLombokequals()hashcoderecordJPA
CodeNotes
Written by

CodeNotes

Discuss code and AI, and document daily life and personal growth.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.