Object Equality and Hash Codes in Java

Naveen AnamaneniUpdated · 6 min read

A bank identifies a customer by their PAN. Two Customer objects loaded at different times, but with the same PAN, are the same customer. Java doesn't know that: by default, equals says two objects are equal only if they're the same object in memory. You teach Java your idea of equality by overriding equals(), and you must override hashCode() with it, or HashSet and HashMap silently misbehave.

Three Versions Side by Side

Contract.java▶ Try it
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
 
public class Contract {
    static class PlainId {
        final String pan;
        PlainId(String pan) { this.pan = pan; }
    }
 
    static class EqualsOnly {
        final String pan;
        EqualsOnly(String pan) { this.pan = pan; }
 
        @Override
        public boolean equals(Object o) {
            return o instanceof EqualsOnly other && pan.equals(other.pan);
        }
    }
 
    static class Customer {
        private final String pan;
        private final String name;
 
        Customer(String pan, String name) {
            this.pan = pan;
            this.name = name;
        }
 
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof Customer other)) return false;
            return pan.equals(other.pan);
        }
 
        @Override
        public int hashCode() {
            return Objects.hash(pan);
        }
    }
 
    public static void main(String[] args) {
        Set<PlainId> a = new HashSet<>();
        a.add(new PlainId("ABCDE1234F"));
        a.add(new PlainId("ABCDE1234F"));
 
        Set<EqualsOnly> b = new HashSet<>();
        b.add(new EqualsOnly("ABCDE1234F"));
        b.add(new EqualsOnly("ABCDE1234F"));
 
        Set<Customer> c = new HashSet<>();
        c.add(new Customer("ABCDE1234F", "Navika"));
        c.add(new Customer("ABCDE1234F", "Navika Anamaneni"));
 
        System.out.println("No equals, no hashCode: " + a.size());
        System.out.println("equals only:            " + b.size());
        System.out.println("equals and hashCode:    " + c.size());
        System.out.println(c.contains(new Customer("ABCDE1234F", "x")));
    }
}
Output
No equals, no hashCode: 2
equals only:            2
equals and hashCode:    1
true

How it works

  1. PlainId overrides nothing. Two objects with the same PAN are different objects, so the set keeps both.
  2. EqualsOnly overrides equals but not hashCode. The objects are "equal", yet the set still keeps both. HashSet looks at hashCode first, and the two default hash codes differ, so it never even calls equals.
  3. Customer overrides both, using the PAN for each. The set sees a duplicate and keeps one, and contains finds a matching customer.

How HashSet and HashMap Use Them

hashCode picks the bucket; equals checks inside it

A hash table is an array of buckets. To find an object it:

  1. Calls hashCode() and turns the number into a bucket index.
  2. Looks only in that bucket, comparing hash codes and then calling equals().

So hashCode decides where to look and equals decides whether it's there. If equal objects have different hash codes, the table looks in the wrong bucket. A later lesson on HashMap internals goes deeper.

The Contract

equals must be:

PropertyMeaning
Reflexivex.equals(x) is true
SymmetricIf x.equals(y), then y.equals(x)
TransitiveIf x.equals(y) and y.equals(z), then x.equals(z)
ConsistentRepeated calls give the same answer while the fields don't change
Null-safex.equals(null) is false, never an exception

hashCode must:

  1. Return the same number every time for an unchanged object.
  2. Return equal numbers for equal objects.
  3. Unequal objects may share a hash code (a collision), but fewer collisions mean faster tables.

Writing Them Correctly

The Customer methods above follow a standard recipe:

  1. if (this == o) return true; is a quick check for the same object.
  2. if (!(o instanceof Customer other)) return false; handles null and other types, and gives a typed variable.
  3. Compare the identifying fields: Objects.equals(a, b) for objects that may be null, == for primitives, Double.compare for doubles.
  4. In hashCode, use the same fields: Objects.hash(pan).

instanceof or getClass()?

CheckEffectUse when
o instanceof Customer otherSubclass objects can equal parent objectsThe class is final, or subclasses don't add identity fields
getClass() != o.getClass()Only exactly the same class can be equalSubclasses add fields that matter for equality

Mixing them carelessly breaks symmetry: a parent saying "yes" to a child that says "no" back. Making value classes final (or using records) avoids the question entirely.

Common Mistakes

Writing equals(Customer other). That overloads instead of overriding; the parameter must be Object (see "Method Overloading vs Overriding").

Using different fields in equals and hashCode. Equal objects then get different hash codes.

Including mutable or derived fields. Use the fields that truly identify the object.

Interview Corner

What is the contract between equals() and hashCode()?

If two objects are equal according to equals, they must return the same hashCode. The reverse isn't required: unequal objects may share a hash code. hashCode must also stay the same for an object as long as the fields used in equals don't change. equals itself must be reflexive, symmetric, transitive, consistent, and return false for null.

What happens if you override equals() but not hashCode()?

Equal objects keep different default hash codes, so hash-based collections treat them as different: a HashSet stores duplicates, and HashMap.get with an equal key returns null, because the lookup goes to the bucket chosen by the hash code and never finds the stored object.

Can two unequal objects have the same hash code?

Yes, that's a collision, and it's allowed. Hash tables handle it by comparing with equals inside the bucket. Too many collisions only slow the table down.

Practice

  1. Easy: Write a Book class equal by ISBN only, with equals and hashCode. Add two copies of the same ISBN to a HashSet and print its size.
  2. Harder: Put a Customer with a mutable pan into a HashSet, change its PAN, and print contains before and after. Explain the result.

Quick check: equals and hashCode

4 questions
1Two objects are equal by equals. What must be true?
2You override equals but not hashCode. What happens in a HashSet?
3What should x.equals(null) return?
4Which helper builds a hash code from several fields?

Key Takeaways

Was this tutorial helpful?

Lesson 153 of 482