Binary SemaphoreBinary Semaphore/learn
mentorshiptopicsroadmapsnotebooks
sign in
CC++JavaPythonJavaScriptTypeScriptGoDSAOperating SystemsNetworksDatabasesSystem DesignTheory and CompilersMaths for CSFrontendBackend and APIsSecurityCloud and DevOpsTestingAIGit and the ShellDebuggingInterviews and Viva
Java
  • memory-and-gc
  • concurrency-traps
  • collections-in-anger
  • equals-and-hashcode
  • interview-questions
  • viva-defence

© 2026 Binary Semaphore

discord

Java

equals and hashCode

The contract between them, and the bug you get in a HashMap when you break it.

This is the one piece of the language where overriding a method incorrectly does not throw, does not warn, and silently corrupts a data structure instead.

The contract

Two rules, and only the second one is ever broken by accident.

If two objects are equal, their hash codes must be equal.

If two hash codes are equal, the objects need not be. Different objects are allowed to collide, and hash-based collections expect it.

So the failure mode is always the same shape: you override equals because you want value comparison, you leave hashCode inherited, and now two objects are equal with different hashes.

Why that breaks a HashMap

A HashMap finds an entry in two steps. It uses the hash to pick a bucket, then uses equals to find the right entry inside it.

Skip step one by leaving hashCode alone and the two equal objects land in different buckets. The map looks in one and never sees the other.

The result is a map where put(k, v) followed by get(k) with an equal key returns null, which reads as an impossible bug until you know this. Every Java developer meets it exactly once.

Writing them

@Override
public boolean equals(Object o) {
  if (this == o) return







More on this is coming, and asking moves it up the list.

Want this written up?

Sign in and tell us. We write the ones people actually ask for, and asking takes one click.

Sign in to ask

Or sit with someone on it instead of waiting for us to write it.

What we have

Nothing yet, and no canonical source worth pointing at.

Book an hour on this
true
;
if (!(o instanceof Order other)) return false;
return id == other.id && Objects.equals(reference, other.reference);
}
@Override
public int hashCode() {
return Objects.hash(id, reference);
}

Three things worth noticing.

The identity check first is a cheap win, not a correctness requirement.

Objects.equals handles nulls on both sides, which is why you use it rather than .equals directly on a field that might be null.

The same fields in both methods. If equals compares id and reference, hashCode uses id and reference. Nothing else. Drifting apart is how this breaks six months later.

instanceof or getClass

instanceof accepts subclasses. getClass() requires exactly the same class.

instanceof is friendlier and can break symmetry: a subclass may consider itself equal to its parent while the parent disagrees, and a.equals(b) differs from b.equals(a). getClass() is strict and rules out a subclass ever being equal to its parent, which is sometimes what you want.

Neither is simply correct. Pick one, know why, and be able to say it out loud.

Records already did this

record Order(long id, String reference) {}

That gives you equals, hashCode and toString over all components, consistently, for free.

If your class exists to hold values, a record is usually the right answer and it removes this entire category of bug. Reach for the handwritten version when you need something a record cannot give you, not by default.

The mutation trap

Even a correct pair breaks if you mutate a field the hash depends on after using the object as a key. The entry is in the map, in a bucket chosen by the old hash, and lookups compute the new one. It is unreachable and still occupying memory.

Keys should be immutable. That is the practical reason.

Under questioning

Expect: what is the contract, what happens if you override one and not the other, can two unequal objects share a hash code, and why should a key be immutable.

The answer that lands is not reciting the contract. It is explaining the two-step lookup, because everything else follows from it.