skip to content

When implementing hashCode() and equals(), how do you decide which fields to include, and why are mutable fields a problem?

level: middleimportance: should knowfreq 55%

answer

  1. Include only stable / final fields
  2. Prefer a stable id (UUID, DB key) over value fields
  3. equals and hashCode share the same field set
  4. Exclude counters, flags, timestamps, mutable collections
  5. No stable field → make it immutable or don't use as a key

basics

~20 s

Base equals/hashCode on fields that don't change after the object is created — ideally a stable identifier. Mutable fields are a problem because if they change while the object is a key in a hash collection, its hashCode changes and the entry can no longer be found.

solid answer

~50 s

The safe rule is to compute hashCode (and equals) only from fields that are stable for the object's lifetime, because hash collections assume a key's hashCode never changes after insertion. If you include a mutable field and later change it, the object's bucket no longer matches its stored position and the entry becomes unfindable. In practice that means: prefer immutable, final fields; if the type has a natural stable identity (a database id, a UUID assigned at construction), base equals/hashCode on that; and exclude fields that callers can mutate (counters, status flags, collections). equals and hashCode must use a consistent field set — every field in equals must be in hashCode — so excluding a field from hashCode means excluding it from equals too. When no field is safely stable, that's often a signal the type shouldn't be used as a hash key at all, or should be made immutable (e.g. a record).

go deeper

for a junior

Knows to use unchanging fields (like an id) for equals/hashCode and to avoid fields that change.

for a middle

Explains the stability requirement and the equals/hashCode lockstep, and can pick fields for a simple class (final id vs mutable email).

for a senior

Distinguishes entity identity (id-based) from value equality, handles the 'no stable field' case by making the type immutable or keying on a separate id, and knows the caching nuance.

for a principal

Sets team conventions (records for value types, id-based equality for entities, immutability as default), and weighs domain-model implications of identity vs value equality across persistence and caching layers.

## Background: the two methods and the contract Every Java object inherits **`equals(Object)`** (logical equality) and **`hashCode()`** (an `int` summary used by hash-based collections). The **contract** between them: if `a.equals(b)` is true, then `a.hashCode() == b.hashCode()`. So whatever fields you compare in `equals` must also feed `hashCode`. Hash collections (`HashMap`, `HashSet`) use `hashCode` to pick a **bucket** and `equals` to match within it. ## The stability requirement Hash collections add a second, often-forgotten requirement: **a stored key's hashCode must stay constant while it's in the collection.** The collection buckets the key once, at insertion, and never re-checks. So the field-selection question is really: *which fields can I rely on never changing while this object lives as a key?* ## How to choose fields 1. **Final / immutable fields are first-class candidates.** A `final` field set in the constructor and never reassigned can't change, so it's safe. 2. **Stable identity beats value.** If the type has a meaningful identifier assigned at creation — a UUID, a DB primary key set once — basing equals/hashCode on that id alone is simple, stable, and fast. Two objects are 'the same' if they share the id, regardless of how other fields drift. 3. **Exclude caller-mutable state.** Fields like a `lastModified` timestamp, a status enum that transitions, a mutable `List`, a running counter — any of these can change after insertion and must be left out. 4. **Keep equals and hashCode in lockstep.** Because of the contract, if you drop a field from hashCode you must drop it from equals. The two methods always reflect the *same* field set. ## What if nothing is stable? If a type has no field that is reliably immutable, that's a strong hint it is a poor hash key. Options: (a) make the type immutable (convert to a `record`, make fields `final`, no setters), so the whole object is a safe key; (b) don't use it as a key — key the map on a separate immutable id instead and store the mutable object as the value. ## Worked example A `User` with a final `id` (UUID) and a mutable `email`. Base equals/hashCode on `id` only. Now `user.setEmail(...)` is harmless: `id` never changes, so the hashCode is stable and the user stays findable in any `HashSet<User>` or as a `HashMap<User, ?>` key. If you had instead included `email` in hashCode, changing the email would strand the entry. ## Edge cases - **Derived/cached values:** caching the hashCode in a field is fine *only if* the inputs are immutable; caching a hashCode of mutable state just hides the bug. - **Collections as fields:** including a mutable `List` in hashCode is doubly dangerous — both reassigning the field and mutating the list's contents change the hash. - **Floating-point and arrays:** orthogonal to mutability but relevant to a correct hashCode (use `Double.hashCode`, `Arrays.hashCode`).

  • If you base equals/hashCode only on an immutable id, what's a downside?
    Two objects with identical value-fields but different ids are 'not equal', and two with the same id but diverged state are 'equal'. That's correct for entity identity but wrong if you wanted value equality — choose deliberately.
  • Is caching hashCode in a field ever okay?
    Yes, when all inputs to the hash are immutable (e.g. String does this). Caching a hash of mutable fields is unsafe because the cached value goes stale and the field-mutation hazard remains.

saying these in an interview costs you the question

  • Including every field in hashCode 'for completeness' without considering mutability.
  • Putting a field in equals but not hashCode (or vice versa) — violates the contract.
  • Hashing on a mutable collection field and assuming it's fine because the reference is final.

context