Skip to content

Equals and HashCode — The Contract, Implementation, and Best Practices

DodaTech Updated 2026-06-28 6 min read

In this tutorial, you will learn about Equals and HashCode. We cover key concepts, practical examples, and best practices to help you master this topic.

The equals and hashCode methods define object equality and hash-based collection behavior in Java, with a strict contract that both must be overridden together. Every class inherits these methods from Object, but the default implementations compare references — which is rarely what you want for value objects.

What You'll Learn

  • The equals() and hashCode() contract
  • How to implement both correctly
  • Common pitfalls and the Objects utility class
  • Automatic generation with Lombok and records

Why It Matters

Incorrect equals()/hashCode() implementations break HashMap, HashSet, HashTable, and any collection that relies on hashing. Objects that are "equal" must have the same hash code, or they will be stored in different buckets — making contains() return false when it should return true.

Real-World Use

JPA entities must implement equals()/hashCode() correctly to work with persistence contexts. DTOs and value objects use them for comparison. Every object stored in a HashSet or used as a HashMap key depends on them.


The Default Behavior

Object a = new Object();
Object b = new Object();
System.out.println(a.equals(b)); // false (different references)
System.out.println(a.equals(a)); // true (same reference)

For most value objects, reference equality is wrong. Two Person objects with the same name and age should be equal.

The equals() Contract

The equals() method must be:

  1. Reflexive: x.equals(x) must be true
  2. Symmetric: x.equals(y) iff y.equals(x)
  3. Transitive: if x.equals(y) and y.equals(z), then x.equals(z)
  4. Consistent: multiple invocations return the same value (assuming no modification)
  5. Non-null: x.equals(null) must be false

A Correct equals() Implementation

public class Person {
    private String name;
    private int age;

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Person person = (Person) obj;
        return age == person.age && Objects.equals(name, person.name);
    }
}

The pattern:

  1. Check reference equality (optimization)
  2. Check null and class equality
  3. Cast and compare significant fields
  4. Use Objects.equals() for nullable fields

The hashCode() Contract

  1. Consistency: Same object, multiple calls — same hash code (assuming no field changes)
  2. Equal objects, equal hash codes: If a.equals(b), then a.hashCode() == b.hashCode()
  3. Unequal objects CAN have same hash code (collisions are allowed)

A Correct hashCode() Implementation

@Override
public int hashCode() {
    return Objects.hash(name, age);
}

Objects.hash() computes a hash code from the fields used in equals().

Manual Implementation

@Override
public int hashCode() {
    int result = 17;
    result = 31 * result + (name != null ? name.hashCode() : 0);
    result = 31 * result + age;
    return result;
}

The prime 31 (or 17/31) produces good distribution and is cheap to compute (JVM optimizes 31 * i to (i << 5) - i).

What Happens When You Break the Contract

public class BadPerson {
    private String name;

    @Override
    public boolean equals(Object obj) {
        // name-based equality — OK
        if (!(obj instanceof BadPerson)) return false;
        return Objects.equals(name, ((BadPerson) obj).name);
    }

    // No hashCode override!
}

Set<BadPerson> set = new HashSet<>();
set.add(new BadPerson("Alice"));
System.out.println(set.contains(new BadPerson("Alice"))); // probably false!

Because BadPerson does not override hashCode(), the two "Alice" objects have different hash codes (default Object.hashCode()), so they land in different buckets.

Using instanceof in equals()

@Override
public boolean equals(Object obj) {
    if (!(obj instanceof Person other)) return false; // pattern matching!
    return age == other.age && Objects.equals(name, other.name);
}

Using instanceof with pattern matching (Java 16+) is cleaner than getClass(), but it violates the symmetry contract if subclasses add fields. The getClass() approach is stricter:

  • getClass(): Person equals Employee returns false (stricter, safer for JPA)
  • instanceof: Person can equal Employee if Employee is a Person (Liskov-friendly)

Lombok @EqualsAndHashCode

import lombok.EqualsAndHashCode;

@EqualsAndHashCode
public class Person {
    private String name;
    private int age;
}

Lombok generates both methods based on all non-static, non-transient fields. Use @EqualsAndHashCode.Exclude to exclude fields.

Records

Records automatically implement equals() and hashCode() based on all components:

public record Person(String name, int age) {}

Person p1 = new Person("Alice", 30);
Person p2 = new Person("Alice", 30);
System.out.println(p1.equals(p2)); // true (record does this for free)

Common Mistakes

  1. Overriding equals() without hashCode(). Hash-based collections break silently.
  2. Using mutable fields in hashCode(). If a field changes, the hash code changes, making the object "lost" in a HashSet.
  3. Forgetting null handling. name.equals(other.name) throws NullPointerException. Use Objects.equals().
  4. Including irrelevant fields. Fields like id (auto-generated) should often be excluded if equality is based on business keys.
  5. Breaking symmetry with inheritance. A subclass that adds fields cannot maintain symmetry when equals() uses instanceof.

Practice Questions

1. What is the contract between equals() and hashCode()?
If a.equals(b) is true, then a.hashCode() == b.hashCode() must be true. The reverse is not required.

2. Why should you use Objects.equals(a, b) instead of a.equals(b)?
Objects.equals() handles null safely — if both are null, it returns true; if one is null, false. a.equals(b) throws NullPointerException if a is null.

3. What fields should be included in equals() and hashCode()?
The same fields should be used in both. Include fields that define business identity (e.g., email, username) and exclude auto-generated IDs or transient state.

4. How do records implement equals() and hashCode()?
The compiler generates both methods based on all record components. The implementation uses getClass() for type comparison.

5. What is the problem with using instanceof in equals() when there are subclasses?
If a subclass adds a field, a.equals(b) might be true while b.equals(a) is false (symmetry violated). Example: Person equals Employee via instanceof, but Employee.equals() checks the extra salary field.

Challenge Question:
Design a User class with fields email (business key), name, and lastLogin (transient). Write equals() and hashCode() using only email. Then create a Set<User> and verify that two users with the same email are considered equal even if names differ. Then add a PremiumUser subclass with an extra tier field — demonstrate the symmetry problem.

FAQ

What is the default `hashCode()` implementation?

Object.hashCode() typically returns the object's memory address (though the JVM can use a random number or a thread-local counter in modern JVMs). It is not guaranteed to be consistent across JVM runs.

What is the significance of 31 in hash code computation?

31 is an odd prime. Multiplying by 31 can be optimized by the JVM to a bit shift and subtraction: 31 * i == (i << 5) - i. The prime reduces hash collisions.

Should I include collections in `equals()`?

Be careful. Including a List or Set in equals() is valid if the collection's elements are compared. However, avoid mutable collections that change frequently.

Can two unequal objects have the same hash code?

Yes. This is a hash collision. The equals() method resolves ambiguity — the hash code narrows down candidates, and equals() confirms identity. A good hash function minimizes collisions.

What is the `Objects.hash()` method?

A utility that computes a hash code from the given values. It internally calls Arrays.hashCode(Object[]). It is the simplest way to implement hashCode() for most classes.

Mini Project

Write a program EqualsDemo.java that:

  1. Creates a Person class with correct equals()/hashCode() using Objects.equals() and Objects.hash()
  2. Creates a BadPerson class with equals() but no hashCode()
  3. Adds instances to both HashSet and HashMap — demonstrate the bug
  4. Adds a Person with a mutable List<String> field and shows the problem of mutating after insertion
  5. Creates a record PersonRecord(String name, int age) and shows that equals/hashCode work correctly
  6. Uses Lombok-style manual generation with @EqualsAndHashCode annotation (simulate without Lombok)

What's Next

Equality is about comparing objects. But how do you order them? Lesson 29 covers Comparable and Comparator — natural ordering via Comparable, custom ordering via Comparator, the Comparator.comparing() Factory methods, and thenComparing() for chaining comparisons.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro