equals(), hashCode() & Object Methods
equals() defines when two objects count as "the same value", and hashCode() must agree with it — together they are the contract every object stored in a HashSet or HashMap has to honour.
Learn equals(), hashCode() & Object Methods in our free Java course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a…
Part of the free Java course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
Before You Start
You should know classes and objects and have used the Collections (especially HashSet and HashMap ). A glance at records shows the shortcut that generates these for you.
What You'll Learn
The Big Idea — Library Shelves
💡 Analogy: A HashMap is a library where hashCode is the shelf number and equals is reading the title to find the exact book. To return a book you first walk to the shelf its hash points to, then check titles on that shelf. If two identical books somehow get different shelf numbers (equal by equals but different hashCode ), the librarian walks to the wrong shelf and swears the book isn't there. That is the silent bug behind "my object is in the set but contains() returns false".
The rule that keeps the library sane: equal objects must hash to the same shelf. Override one, override the other.
1️⃣ The Default Behaviour (identity)
Every class inherits equals and hashCode from Object . By default equals is just == (reference identity) and hashCode is derived from the object's memory identity. So two objects with identical fields are not equal, and a HashSet happily stores both as duplicates.
2️⃣ Implementing equals and hashCode Correctly
A correct equals follows a fixed shape: identity fast-path, an instanceof check (which also rejects null ), a cast, then a field-by-field comparison. The matching hashCode uses Objects.hash(...) over the same fields . Add a readable toString while you're there.
3️⃣ The Classic Bug — equals without hashCode
Override equals but forget hashCode and you get the most infamous Java bug: objects that are equal but have different hash codes. They land in different buckets, so set.contains and map.get fail to find them even though an equal object is right there.
4️⃣ Records Give You All Three for Free
Declare the same value type as a record and the compiler generates equals , hashCode , and toString — all consistent, all derived from the components. For simple immutable data carriers this is the safest and shortest option.
🧠 Quick Recall
Answer: illegal — it breaks the contract and breaks hash collections. The reverse (different objects sharing a hashCode) is legal; that's just a collision, which equals resolves.
Answer: because a might be null . Objects.equals(a, b) is null-safe — it returns true if both are null and never throws an NPE.
Answer: no — == compares references and new String makes a distinct object. .equals is true . Always use .equals for String value comparison.
🎯 YOUR TURN — Equal by ISBN
Override equals and hashCode on Book so two books with the same ISBN are one in a HashSet .
🧩 MINI-CHALLENGE — Record as a map key
Use a record Coord(int x, int y) as a HashMap key. Re-putting the same coordinate overwrites, and lookups by a fresh Coord must hit.
Common Errors
- ❌ Overriding equals but not hashCode. Hash collections silently break. Fix: always override both, over the same fields.
- ❌ Wrong equals signature. equals(Point p) overloads instead of overriding. Fix: the parameter must be Object ; add @Override so the compiler catches it.
- ❌ Using == to compare fields that are objects. It compares references. Fix: use Objects.equals for object fields, == only for primitives.
- ❌ Including mutable fields in hashCode. If a key's hash changes after insertion, it's lost in the map. Fix: base equals/hashCode on stable (ideally final) fields.
- ❌ Forgetting the null/type check. Casting first throws on a wrong type. Fix: guard with instanceof , which is false for null and wrong types.
- ❌ Hand-writing all this when a record would do. More code, more bugs. Fix: use a record for plain immutable data.
Pro Tips
- 💡 Let your IDE or a record generate them. Hand-rolled equals/hashCode is a top source of subtle bugs.
- 💡 Use the same fields in both methods — and only fields that define identity, not caches or timestamps.
- 💡 Keep value types final to avoid the subclass symmetry trap in equals.
- 💡 Always override toString too. Good toString output makes logs and debugger views instantly readable.
📋 Quick Reference
Concept
Syntax
Note
identity fast path
if (this == o) return true;
First line of equals
type/null check
if (!(o instanceof T)) return false;
false for null too
field compare
Objects.equals(a, b)
Null-safe
primitive compare
x == p.x
== fine for primitives
hashCode
Objects.hash(f1, f2)
Same fields as equals
contract
equal ⇒ same hashCode
Reverse not required
override marker
@Override
Catches bad signatures
toString
@Override String toString()
Readable output
value vs identity
.equals vs ==
Value vs reference
record
record T(...)
All three for free
use in
HashSet / HashMap
Needs correct pair
📚 Resources / Keep Learning
- 📘 Object (equals, hashCode, toString) — Java 21 API (docs.oracle.com)
- 📘 Objects utility class — Java 21 API (docs.oracle.com)
- 📗 Records — dev.java tutorial
❓ Frequently Asked Questions
🎉 Lesson Complete!
You now know what the default equals / hashCode do, the contract that binds them, how to write both correctly with instanceof , Objects.equals and Objects.hash , why a missing hashCode silently breaks hash collections, and how records hand you all of it for free.
Next up: Immutability & Defensive Copying — building objects that can't be corrupted after construction.
Practice quiz
What does the default Object.equals compare?
- The fields of the objects
- Object identity (same reference)
- The toString output
- The class names
Answer: Object identity (same reference). Without an override, equals returns true only when both references point to the very same object (==).
The equals/hashCode contract requires that...
- equal objects have equal hashCodes
- unequal objects have unequal hashCodes
- hashCode is always 0
- equals is faster than hashCode
Answer: equal objects have equal hashCodes. If a.equals(b) is true then a.hashCode() must equal b.hashCode(). The reverse is not required — collisions are allowed.
What breaks if you override equals but NOT hashCode?
- Nothing
- HashSet/HashMap lookups fail to find equal objects
- Compilation fails
- toString breaks
Answer: HashSet/HashMap lookups fail to find equal objects. Hash-based collections use hashCode to pick a bucket; if equal objects have different hashCodes they land in different buckets and can't be found.
Which helper computes a hash from several fields?
- Objects.hash(x, y)
- Objects.equals(x, y)
- Math.hash(x, y)
- Arrays.hash(x, y)
Answer: Objects.hash(x, y). Objects.hash(field1, field2, ...) returns a combined hash and handles nulls for you.
Which helper null-safely compares two fields in equals?
- a == b
- a.equals(b)
- Objects.equals(a, b)
- Objects.compare(a, b)
Answer: Objects.equals(a, b). Objects.equals(a, b) returns true if both are null or a.equals(b), avoiding a NullPointerException.
Do records generate equals and hashCode for you?
- No, you must write them
- Yes, based on all components
- Only equals
- Only hashCode
Answer: Yes, based on all components. A record automatically gets equals, hashCode and toString derived from its components — no boilerplate.
A correct equals should first check...
- this == o for a fast path
- the toString
- the hashCode
- the array length
Answer: this == o for a fast path. if (this == o) return true; is a quick identity short-circuit before the more expensive field comparison.
How should equals handle a null or wrong-type argument?
- Throw an exception
- Return true
- Return false (e.g. via instanceof)
- Ignore it
Answer: Return false (e.g. via instanceof). instanceof returns false for null and for the wrong type, so 'if (!(o instanceof T)) return false;' handles both safely.
What is a key property of equals?
- It must be symmetric: a.equals(b) == b.equals(a)
- It must be random
- It must compare hashCodes
- It must be slower than hashCode
Answer: It must be symmetric: a.equals(b) == b.equals(a). equals must be reflexive, symmetric, transitive, and consistent — symmetry being a classic one people break with inheritance.
What does Object.toString return by default?
- The field values
- ClassName@hexHashcode
- An empty string
- null
Answer: ClassName@hexHashcode. The default is getClass().getName() + '@' + Integer.toHexString(hashCode()) — rarely useful, so override it.
Continue this course
- Previous: The Java HTTP Client
- Next: Immutability & Defensive Copying