equals() and hashCode() Explained in Java

Beginner
7 min read· Backend & Databases

In Java, equals() defines when two objects are considered equal, and hashCode() returns an int used to bucket objects in hash-based collections. They are bound by a contract: equal objects must have equal hash codes. Break it and HashMap, HashSet, and Hashers silently misbehave — losing entries or storing duplicates. Understanding this contract is essential for correct domain objects and a favourite interview topic.

Think of hashCode as a mailbox number and equals as the name

In an apartment building, hashCode is the mailbox number that tells the postman roughly where to look, and equals is the resident name that confirms the exact match. If two identical residents were assigned different mailbox numbers (equal objects, different hash codes), the postman would check the wrong box and never find them. The rule "same person, same mailbox" is exactly the equals/hashCode contract.

Step by Step

1 / 5

Key Concepts

The Contract

Equal objects must have equal hash codes. Also: equals must be reflexive, symmetric, transitive, consistent, and false for null. Violations cause subtle collection bugs.

Objects.hash / Objects.equals

JDK helpers that build correct hashCode and null-safe equals from a field list, e.g. Objects.hash(amount, currency) — the idiomatic modern way.

Mutable Key Pitfall

If you use a mutable object as a map key and then change a field used in hashCode, the entry lands in the wrong bucket and becomes unreachable.

Collision

Two unequal objects sharing a hash code. This is allowed and handled by the collection; only the reverse (equal objects with different hashes) breaks the contract.

Key Facts

  • Records and enums generate a correct equals/hashCode pair for you — use them for value types.
  • Overriding equals but not hashCode is the single most common cause of "my object disappeared from the HashSet" bugs.
  • hashCode does not have to be unique or expensive; it should be fast and spread values reasonably across buckets.

Real-World Applications

Deduplicating with a HashSet

Putting domain objects into a HashSet to remove duplicates only works if equals and hashCode reflect the fields that define uniqueness — otherwise logically-equal items are stored twice.

Using value objects as map keys

A CurrencyPair or OrderId used as a HashMap key must implement the contract correctly (or be a record) so lookups by an equal-but-different instance succeed.

Frequently Asked Questions

What happens if I override equals but not hashCode?

Two objects you consider equal may return different hash codes, so a HashMap or HashSet stores them in different buckets. You then get duplicates in a set, or fail to retrieve a value you inserted — a classic, hard-to-spot bug.

Does hashCode have to be unique?

No. Hash codes can collide; the collection resolves collisions with equals. The only firm rule is that equal objects must share a hash code. Uniqueness is neither required nor achievable for large data sets.

What are the equals() rules I must follow?

equals must be reflexive (x equals x), symmetric (x equals y implies y equals x), transitive, consistent across calls, and x.equals(null) must be false. Auto-generated implementations satisfy these by construction.

Should I write equals and hashCode by hand?

Prefer generated implementations — a record, an IDE-generated pair using Objects.hash/Objects.equals, or Lombok. Hand-writing them is error-prone, especially keeping the field sets in sync.

Related Topics