skip to content

What does the default Object.hashCode() return, and what does 'not stable across JVM runs' mean for it?

level: middleimportance: should knowfreq 40%

answer

  1. Default = per-instance identity hash, not address
  2. GC moves objects -> address can't be the hash
  3. Stable within a run, NOT across runs
  4. Distinct objects -> different codes (matches == equals)
  5. System.identityHashCode bypasses overrides

basics

~20 s

If a class doesn't override hashCode(), it gets an identity hash: a number tied to that specific object instance, the same every time you ask during one run. It is not the memory address, and a fresh run of the program can produce different numbers.

solid answer

~50 s

Without an override, an object inherits `Object.hashCode()`, which returns an **identity hash code** — a per-instance integer the JVM computes (often lazily) and then keeps stable for that object's lifetime within the run. It is *not* the memory address (the garbage collector relocates objects, so the address can't be the identity), and it is *not* derived from the object's fields, so two field-identical-but-distinct objects get different codes — matching the default `equals`, which is reference identity. 'Not stable across JVM runs' means there is no guarantee the same logical object yields the same int when you restart the program; the identity hash is generated per run (and some JVM modes randomize the seed). You can still obtain this value for any object, even one that overrides hashCode, via `System.identityHashCode(obj)`. Practical consequence: never persist or transmit a hashCode expecting it to be reproducible later.

code

java · 9 lines
java
class Box { int v; Box(int v){ this.v = v; } }  // no hashCode override

Box a = new Box(5);
Box b = new Box(5);          // same field value, different instance

a.hashCode() == a.hashCode();          // true: stable within the run
a.hashCode() == b.hashCode();          // almost always FALSE: identity, not value
System.identityHashCode(a);            // the identity hash, bypassing any override
// On a fresh JVM run, a.hashCode() may print a different number than last time.

go deeper

for a junior

Knows that without an override hashCode is tied to the specific object, stable during the run, and that equal-content objects don't automatically share it.

for a middle

Explains it's an identity hash (not the address, because GC moves objects), consistent within a run but not across runs, and matches the identity-based default equals.

for a senior

Discusses where the identity hash is stored (object header), JVM hashCode strategies / randomization, System.identityHashCode and IdentityHashMap, and why persisting hashCodes is unsafe.

for a principal

Reasons about header-word pressure from stored identity hashes, biased-locking/header interactions historically, and sets guidance that any cross-process identifier must be an explicit field, never a hashCode.

## The default behavior Every class that does **not** override `hashCode()` uses `java.lang.Object`'s implementation. This returns an **identity hash code**: an `int` associated with the *identity* of that specific object instance. Key properties: - **Per-instance, not per-value.** Two *distinct* objects — even with identical field contents — get (almost always) *different* identity hashes. This is deliberately consistent with `Object.equals`, whose default is reference identity (`a == b`): only the very same object is equal to itself, and only it shares its identity hash. - **Stable within a run.** Once computed for an object, the value is cached (commonly in the object's header word) and returned unchanged for the rest of that object's life, satisfying the contract's consistency clause. - **Not the memory address.** A common misconception is that it's the pointer. It cannot be, because the JVM's garbage collector **moves** objects in memory during compaction; if the hash were the address, it would change when the object moved, breaking consistency. So the JVM computes a separate identity value (e.g. from a thread-local PRNG, or a global counter, depending on the `-XX:hashCode` strategy) and stores it. ## 'Not stable across JVM runs' The contract only promises consistency *within a single execution*. The identity hash is generated **fresh each run** — and several JVM configurations seed it from a randomized source, so the same logical object created in two different program executions will typically receive **different** identity hashes. Therefore: - You must **never persist** a hashCode (to a file, database, or cache key on disk) and rely on it matching after a restart. - You must **never send** a hashCode over the network as a stable identifier between processes. - Reproducible test assertions must not hard-code expected hashCode values for default-hash objects. The same caveat technically applies even to *overridden* hashCodes if they depend on per-run data, but a well-written value-based hashCode (derived purely from stable field values) **will** reproduce across runs — it's the *identity* default specifically that won't. ## Accessing it explicitly `System.identityHashCode(obj)` returns the identity hash for *any* object, **bypassing** any overridden `hashCode()`. This is occasionally useful for identity-based diagnostics or `IdentityHashMap`, which keys by `==` and identity hash rather than `equals`/`hashCode`. ## Why this matters Understanding the default explains two everyday facts: (1) why putting an *un-overridden* value object into a `HashSet` and then looking it up with a freshly constructed equal object **fails** (different identity hashes), and (2) why you should override *both* `equals` and `hashCode` together when you want value semantics. The default is correct and self-consistent; it simply implements *identity*, not *equality of content*.

  • Is the default hashCode the object's memory address?
    No. The GC can relocate objects during compaction, so the address isn't stable. The JVM instead computes a separate identity value (per its hashCode strategy) and caches it in the object header, keeping it stable for the object's lifetime in that run.
  • If I override hashCode from immutable fields, will it reproduce across runs?
    Yes, generally. A value-based hashCode computed purely from stable field values produces the same int wherever those values are the same, across runs and JVMs. The cross-run instability is specific to the identity default (and any hash depending on per-run state).

The identity hash is like a coat-check ticket number handed out when you arrive: unique to you for today's visit and the same all evening, but the numbering restarts (and may be shuffled) tomorrow. It says nothing about what your coat looks like — only that this ticket is yours, this visit.

saying these in an interview costs you the question

  • Saying the default hashCode is the memory address
  • Assuming two field-equal objects share the default hashCode
  • Expecting default hashCodes to be reproducible after restart
  • Confusing System.identityHashCode with the overridden hashCode

context