skip to content

When should a class be compared by identity (==) versus by logical equality (equals()), and how does that map to entity vs value object modeling?

level: seniorimportance: should knowfreq 55%

answer

  1. entity = identity over time (User stays same after edits)
  2. value object = defined by state (Money(5,USD) interchangeable)
  3. value object → override equals/hashCode, make immutable, use record
  4. entity → compare by identity or stable id, not all fields
  5. wrong choice breaks HashSet/HashMap (collisions or misses)

basics

~20 s

Use identity (==) for objects that represent a unique thing with a lifecycle (entities) — two are the same only if they're literally the same object. Use equals() (value comparison) for objects defined purely by their data (value objects), like money or a date.

solid answer

~50 s

The choice mirrors a domain-modeling distinction. An entity has an identity that persists independent of its attributes — a User is still the same user after changing its email; two User objects representing different people should never be equal even if all current fields match. Such types are naturally compared by identity, often via a stable id field rather than raw ==. A value object is defined entirely by its state — Money(5, USD) is interchangeable with any other Money(5, USD); these should override equals()/hashCode() to compare by value, and are ideally immutable. Getting this wrong causes bugs: giving an entity a value-based equals() can make two distinct rows collide in a HashSet; leaving a value object with the default identity equals() means logically-equal instances won't be found as map keys. Java reinforces this: records auto-generate value equality (great for value objects), while JPA entities are typically compared by a business or surrogate key, not by all fields.

go deeper

for a junior

Recognizes that some classes compare by value (override equals) and some by identity, with simple examples like Money vs a unique account.

for a middle

Can override equals/hashCode for a value object and knows the default is identity; starts to see why entities differ.

for a senior

Articulates entity vs value object, picks the right equality strategy, and explains the collection bugs each mistake causes; knows records and JPA-key guidance.

for a principal

Treats identity as part of the domain contract, designs aggregate boundaries and id strategies, and weighs immutability, distribution (cross-JVM identity), and ORM session semantics when defining equality.

## Two questions, two tools Java gives you `==` (identity: *same object?*) and `equals()` (logical equality: *same value?*). Choosing which to honor for a given class is really a **domain-modeling decision**, captured by the classic distinction between **entities** and **value objects** (from Domain-Driven Design). ## Value objects — compare by value A **value object** is defined *entirely by its attributes*. It has no identity of its own; any two instances with the same data are interchangeable. - Examples: `Money(amount, currency)`, a `LocalDate`, a `Color(r,g,b)`, a coordinate `Point(x,y)`. - `Money(5, USD)` is the *same* as any other `Money(5, USD)` — you'd never care *which* object you hold. For value objects you should **override `equals()` and `hashCode()`** to compare by state, and ideally make them **immutable** (so their identity-vs-value question can never drift). Java **records** do exactly this automatically: ```java record Money(BigDecimal amount, Currency currency) {} // equals/hashCode/toString generated from the components ``` ## Entities — compare by identity An **entity** has an identity that is *independent of its attributes* and persists over time and state changes. - A `User` is the same user before and after changing its name, email, or password. - Two `User` objects representing **different people** must **never** be equal, even if every current field happens to match (two people both named "Jane Doe"). So an entity's notion of "same" is **identity**, not field-by-field value. Concretely: - **In-memory, transient** entities are often left with the **default identity `equals()`** (`==`), which is correct: each object *is* its own identity. - **Persistent** entities (e.g. JPA) are usually compared by a **stable business key or surrogate id**, because the same database row may be loaded into different object instances across sessions, and you want those to be "equal". Comparing *all* fields is wrong (mutable fields change), and using the auto-generated id naively is risky before it's assigned. ## Why getting it wrong hurts Hash-based and sorted collections rely on `equals()`/`hashCode()`: 1. **Value object with default (identity) equals()** → two logically-equal instances are treated as different, so a `HashMap` lookup with a freshly-built key *misses*, and a `HashSet` stores duplicates. 2. **Entity with naive value-based equals() over all fields** → two distinct real-world entities with coincidentally-equal fields **collide** (one disappears from a `Set`), and an entity's hash **changes** when a mutable field changes, corrupting its position in a hash structure (the "mutable key" hazard). ## How Java nudges you - **Records** = built-in value semantics → reach for them for value objects. - **Enums** are singletons; comparing them with `==` is both correct and idiomatic (each constant is a unique identity). - **JPA/Hibernate** guidance: implement `equals`/`hashCode` on a stable key, not on all mutable fields, precisely because an entity's equality is about identity, not current state. ## Decision checklist - *Is the object interchangeable with any other holding the same data?* → **value object**, override `equals()`/`hashCode()`, prefer immutable / a record. - *Does it have a lifecycle and a distinct identity regardless of its fields?* → **entity**, compare by identity or a stable id, not by all fields. - *Do I literally need "the same object"?* (caching, sentinels, enums) → use `==`.

  • Why is comparing two JPA entities by all their fields a bad idea?
    Mutable fields change over the entity's life, so its equals/hashCode would change too — corrupting its slot in hash collections and making the same row loaded twice possibly unequal. Use a stable business key or carefully-managed surrogate id instead.
  • Why is == the right way to compare enum constants?
    Each enum constant is a singleton with a unique identity, so == is correct, faster, null-safe (no NPE on a null left side), and fails at compile time if you compare incompatible enum types.

saying these in an interview costs you the question

  • Overriding equals() over all mutable fields of an entity
  • Leaving a value object with default identity equals()
  • Including mutable fields in an entity's hashCode
  • Assuming every class should override equals()
  • Comparing enums with equals() instead of == (== is correct and null-safe)

context