Chaturmind
LearnDSASystem DesignInterview PrepDevOpsEngineering GrowthBlog
Start learning
Chaturmind

Structured learning paths for engineers who want to go deep. Written by practitioners.

Learn

  • Java
  • DSA
  • System Design
  • Spring Boot
  • AI / ML
  • DevOps
  • Engineering Growth
  • Java Interview Prep

Company

  • About
  • Blog
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Chaturmind. All rights reserved.

👥10,051visitors

Built for engineers who want to go deep.


← Java Core Fundamentals

Object-Oriented Programming

  • Classes and Objects
  • Inheritance and Polymorphism
  • Interfaces and Abstract Classes

Collections Framework

  • List, Set, and Map
  • Generics
  • Collections Framework Recap
  • HashMap Deep Dive
  • TreeMap & LinkedHashMap
  • Iterators & Modification Semantics

Exceptions & Best Practices

  • Exception Handling
  • equals() and hashCode()
  • String Manipulation

JVM Internals & Memory Management

  • JVM Memory Areas
  • Garbage Collection Fundamentals
  • Modern Garbage Collectors
  • Memory Leaks in Java
  • GC Tuning & Diagnostics
Chaturmind
← Java Core Fundamentals

Object-Oriented Programming

  • Classes and Objects
  • Inheritance and Polymorphism
  • Interfaces and Abstract Classes

Collections Framework

  • List, Set, and Map
  • Generics
  • Collections Framework Recap
  • HashMap Deep Dive
  • TreeMap & LinkedHashMap
  • Iterators & Modification Semantics

Exceptions & Best Practices

  • Exception Handling
  • equals() and hashCode()
  • String Manipulation

JVM Internals & Memory Management

  • JVM Memory Areas
  • Garbage Collection Fundamentals
  • Modern Garbage Collectors
  • Memory Leaks in Java
  • GC Tuning & Diagnostics
HomeLearnJavaJava Core FundamentalsExceptions & Best Practices
✓ FreeIntermediate· 6 min read

equals() and hashCode()

The contract between equals and hashCode — the most commonly broken Java rule.

Published September 21, 2026


equals() and hashCode()

Every Java object inherits two methods from Object that decide when two objects count as "the same":

  • equals(Object other) answers "do these two objects represent the same value?"
  • hashCode() returns an int summarizing the object. Hash-based collections (HashMap, HashSet) use it to decide which bucket to look in, before ever calling equals.

By default, both are based on identity: an object equals only itself, and two separate objects with identical fields are not equal. For value-like classes (a Money, an Email, a Point, a DTO used as a map key) that's wrong. You override both, and you must keep them consistent, or collections quietly stop working.

Advertisement

The contract

equals must be:

  1. Reflexive: x.equals(x) is true.
  2. Symmetric: x.equals(y) ⇔ y.equals(x).
  3. Transitive: if x.equals(y) and y.equals(z), then x.equals(z).
  4. Consistent: repeated calls give the same result while the objects are unchanged.
  5. Non-null: x.equals(null) is false, and never throws.

And the rule that ties the two methods together:

If a.equals(b), then a.hashCode() == b.hashCode().

The reverse isn't required. Unequal objects may share a hash code, which is a collision, and it's legal, just slower. But equal objects with different hash codes break every hash-based collection.

What goes wrong if you override only equals

class User {
    private final String email;
    User(String email) { this.email = email; }

    @Override public boolean equals(Object o) {
        return o instanceof User u && email.equals(u.email);
    }
    // hashCode() NOT overridden → still identity-based
}

Set<User> users = new HashSet<>();
users.add(new User("asha@example.com"));
users.contains(new User("asha@example.com"));   // false!  (and adding it again creates a duplicate)

A HashSet first uses hashCode() to pick a bucket. The two objects have different identity hash codes, so the lookup searches the wrong bucket and never calls equals at all. The objects are "equal", yet the set can't find the match.

A correct implementation

public final class Money {
    private final long amountInPaise;
    private final String currency;

    public Money(long amountInPaise, String currency) {
        this.amountInPaise = amountInPaise;
        this.currency = Objects.requireNonNull(currency);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;                              // fast path: same object
        if (!(o instanceof Money other)) return false;           // also handles null
        return amountInPaise == other.amountInPaise && currency.equals(other.currency);
    }

    @Override
    public int hashCode() {
        return Objects.hash(amountInPaise, currency);            // exactly the same fields as equals
    }
}

Guidelines:

  • Use the same fields in both methods. Every field compared in equals should feed hashCode.
  • Compare primitives with == (use Double.compare for double/float, because of NaN and -0.0), and objects with Objects.equals if they may be null.
  • Objects.hash(...) is convenient. For very hot code, a hand-rolled 31 * h + field avoids creating a varargs array.
  • Records (record Money(long amountInPaise, String currency) {}) generate correct equals, hashCode and toString from their components. That's often the simplest answer for value types (see Records). IDEs and Lombok can generate these methods too.

instanceof or getClass()?

if (!(o instanceof Money other)) return false;                  // accepts subclasses
if (o == null || getClass() != o.getClass()) return false;     // requires the exact same class

With inheritance, instanceof can break symmetry. If ColoredPoint extends Point adds a colour and compares it, then point.equals(coloredPoint) may be true (Point ignores colour) while coloredPoint.equals(point) is false. Options:

  • Make value classes final (or records), so no subclasses exist, and then instanceof is safe and idiomatic.
  • Use getClass() when subclasses must never equal their parent.
  • Prefer composition (a ColoredPoint has a Point) over extending a value class to add state.

Mutable fields in keys: the "lost object" bug

Set<Point> points = new HashSet<>();
Point p = new MutablePoint(1, 2);
points.add(p);
p.setX(5);                 // hash changes; p sits in the bucket for its OLD hash
points.contains(p);        // false — looks in the new bucket
points.remove(p);          // fails — the object is now effectively stuck in the set

Objects used as HashMap keys or HashSet elements should be immutable, or at least the fields used in equals/hashCode must never change while the object is in the collection.

JPA entities: a special case

For database entities, equality based on all fields is usually wrong: fields change and relationships are lazy-loaded. Equality based on a database-generated ID is tricky too, because a new entity has no ID until it's saved, so its hash would change. Common solutions: a natural business key that never changes (like an ISBN), or a UUID assigned at construction, or equality by ID with a constant hashCode. Whatever you choose, keep it stable across the entity's lifecycle.

== vs equals

  • == on references asks "same object?". On primitives it compares values.
  • equals asks "same value?", as defined by the class.
  • String, Integer and other wrappers override equals. Comparing them with == works sometimes, because of string interning and the Integer cache for −128..127, which makes the resulting bugs intermittent and hard to find. Always use equals for objects.

Follow-up questions this topic invites — and their answers

Q: Why must you override hashCode when you override equals? A: Hash-based collections look up an object's bucket by its hash code first, and only call equals within that bucket. If equal objects have different hash codes, lookups search the wrong bucket, so contains fails and duplicates get inserted.

Q: Can two unequal objects have the same hash code? A: Yes, that's a collision, and it's allowed. Collections handle it by comparing with equals inside the bucket. Many collisions degrade performance (HashMap falls back to tree bins to limit the damage), so a good hashCode spreads values well.

Q: What happens if hashCode returns a constant, like 42? A: Correctness is preserved (equal objects share a hash), but every element lands in the same bucket. Operations degrade toward O(n), or O(log n) with HashMap's tree bins. It's legal, but slow.

Q: Why is using a mutable object as a HashMap key dangerous? A: If a field used in hashCode changes after insertion, the entry stays in the bucket for the old hash, and lookups using the new hash miss it. The entry becomes unreachable, a subtle memory leak and logic bug. Use immutable keys.

Q: How do records implement equals and hashCode? A: The compiler generates them from all components: equals compares each component (using == for primitives, with correct float/double semantics, and Objects.equals for references), and hashCode combines the components' hash codes. Array components are compared by reference, so prefer List in records.

Advertisement

Previous

Exception Handling

Next

String Manipulation

AI Tutor

Lesson: equals() and hashCode()

Quick actions

AI responses can be inaccurate. Verify critical information.