Basics

9. The equals() / hashCode() Contract

equals() and hashCode() must be overridden together and satisfy a strict contract; violating it silently breaks HashMap, HashSet, and all hash-based data structures.

Java 21 (core language)

Explanation

The contract: if two objects are equal according to equals(), they must have the same hashCode(). The converse is not required โ€” unequal objects may share a hash code (collision). If you override one without the other, hash-based collections break silently.

For example, if you override equals() but not hashCode(), two "equal" objects will hash to different buckets in a HashMap and appear as separate entries. You'll put one and never find it with a get using the equal key.

Modern Java: use java.util.Objects.equals() (null-safe) and java.util.Objects.hash() in your implementations, or let the IDE generate them. Better still, use records (Java 16+) โ€” they automatically provide correct equals(), hashCode(), and toString() based on all components.

Example

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

public class EqualsHashCode {
    // BAD: only equals, no hashCode
    static class BadKey {
        final String value;
        BadKey(String v) { this.value = v; }
        @Override public boolean equals(Object o) {
            return o instanceof BadKey b && value.equals(b.value);
        }
        // No hashCode override โ€” BROKEN for HashMap!
    }

    // GOOD: both overridden correctly
    static class GoodKey {
        final String value;
        GoodKey(String v) { this.value = v; }
        @Override public boolean equals(Object o) {
            return o instanceof GoodKey g && Objects.equals(value, g.value);
        }
        @Override public int hashCode() {
            return Objects.hash(value);
        }
    }

    // BEST in Java 16+: record does it automatically
    record RecordKey(String value) {}

    public static void main(String[] args) {
        Map<BadKey, String> bad = new HashMap<>();
        bad.put(new BadKey("test"), "hello");
        System.out.println(bad.get(new BadKey("test"))); // null! (wrong bucket)

        Map<GoodKey, String> good = new HashMap<>();
        good.put(new GoodKey("test"), "hello");
        System.out.println(good.get(new GoodKey("test"))); // "hello" โœ“

        Map<RecordKey, String> rec = new HashMap<>();
        rec.put(new RecordKey("test"), "hello");
        System.out.println(rec.get(new RecordKey("test"))); // "hello" โœ“
    }
}

Sources