In Domain-Driven Design, what is the core difference between an Entity and a Value Object, and why does that difference change how you implement equality checks (equals/hashCode) for each?
answer
- Identity vs structural equality
- equals() compares id (Entity) vs all fields (VO)
- VO = interchangeable if equal
- surrogate DB id != domain identity
basics
~20 sAn Entity is something with its own identity that stays the same over time even if its details change — like a person has one ID for life. A Value Object has no identity of its own; it's just described by its data, so two Value Objects with the same data are treated as equal and interchangeable.
solid answer
~50 sEntities are defined by a persistent identity (usually an ID field) that remains stable across the object's lifetime, independent of attribute changes — two Entities are the same only if their identity matches, even if every other field differs. Value Objects have no identity at all; they're fully defined by their attribute values, so two Value Objects with identical attributes are considered equal and interchangeable ('structural equality'). This changes equals()/hashCode(): an Entity's equals() should compare only the identity field, while a Value Object's equals() must compare every attribute that makes up its value. Getting this backwards causes real bugs — e.g., two distinct Order entities that happen to share the same customer and total collapsing into 'equal' in a Set, or two logically identical Money(10, USD) instances failing equals() because it fell back to reference equality.
go deeper
Should state the core rule correctly — Entity = identity-based equality, Value Object = attribute-based equality — and give one clean example of each. Doesn't need to discuss hashCode pitfalls or ORM mapping details.
Should correctly implement equals()/hashCode() for both cases in code, and recognize when a class in an existing codebase is mis-modeled (e.g., a VO using default reference equality).
Should discuss the ORM/persistence mapping implications (embeddable vs. entity table), the hashCode-mutability trap, and be able to spot a mis-modeled domain type in a design review with a concrete production consequence.
Should frame the distinction as a modeling decision driven by ubiquitous language and business need for traceability, not implementation convenience, and coach a team on the cost of getting it wrong at scale (data duplication, audit gaps, silent collection bugs).
## The one question that splits the model Domain-Driven Design (Eric Evans, 2003) splits domain objects into two tactical building blocks based on one question: does this thing have a continuous identity that domain experts track through time, or is it just a descriptive measurement? - An **Entity** is the former — a `Customer`, an `Order`, a `BankAccount` — something with a lifecycle: it is created, its attributes change over time (a customer's email, an order's status), and yet the business still calls it 'the same' object because it carries an identity (a `customerId`, an `orderNumber`) that never changes. - A **Value Object** is the latter — `Money`, an `Address`, a `DateRange`, a `Color` — something with no independent existence; it exists only to describe an attribute of something else, and two Value Objects holding the same data ARE the same value, fully interchangeable, the way two $10 bills are interchangeable regardless of serial number in casual usage. ## What that does to equality Mechanically, this split drives `equals()`/`hashCode()` (or your language's equivalent). - An Entity's `equals()` must compare only the identity field: `return this.id.equals(other.id)`, ignoring every other attribute — because an Order with a corrected shipping address is still 'the same order' as before the correction. - A Value Object's `equals()` must compare every field that constitutes the value: `return this.amount.equals(other.amount) && this.currency.equals(other.currency)` — because `Money(10, USD)` built in one part of the code and `Money(10, USD)` built elsewhere are not 'the same instance' but must be treated as equal in every meaningful sense: set membership, map keys, assertions in tests, deduplication. ## When the model lies This distinction exists because software objects model how domain experts actually reason about the business, and getting it wrong produces a model that lies. - If `Money` is modeled as an Entity (a database-generated id, reference equality), two numerically identical prices fail equality checks, break caching, and multiply in collections where they should be deduplicated — the model now claims '$10 here' and '$10 there' are different facts, which no domain expert would agree with. - Conversely, if `Customer` is modeled as a Value Object (equality by name+email), two different customers who happen to share a name and a shared/generic email silently collapse into 'the same customer' in a Set or Map, merging their order histories — a serious correctness and even compliance bug. ## The trade-off The trade-off is genuine. | Modeling choice | What it buys | What it costs | |---|---|---| | **Choosing Entity** | buys traceability: you can answer 'which one' after a mutation, keep an audit trail, and support in-place updates with optimistic locking on a version field | The cost is more machinery — an identity-generation strategy (UUID, sequence, natural key), `equals()`/`hashCode()` discipline around the transient-id problem, and reasoning about concurrent mutation. | | **Choosing Value Object** | buys simplicity: no identity bookkeeping, safe to share/cache/pass by value across threads because immutability removes race conditions, and equality 'just works' for testing and deduplication | The cost is losing the ability to distinguish two equal-by-value instances — if the business later needs to track 'this specific $10 payment' as opposed to 'a $10 payment', the type was under-modeled and needs promotion to an Entity. | ## The failure signature in production In production, the most common failure signature is a hashCode/equals mismatch surviving code review because unit tests use small enough test data that the bug doesn't trigger. A classic case: a team models an `Address` as a JPA `@Entity` with an auto-generated `id` primary key (because 'everything in the database needs an id') but implements `equals()` using a default that includes ALL fields including that generated id. Two Address rows inserted with identical street/city/zip but different ids then compare unequal — deduplication logic that assumed value semantics silently stops working, and duplicate addresses accumulate. The fix is either: 1. don't give Address a surrogate id at all (true Value Object, embedded/owned by its parent Entity, e.g., JPA `@Embeddable`), or 2. explicitly implement `equals()`/`hashCode()` to ignore the surrogate id and compare only the descriptive fields. The general lesson: the presence of a database primary key is an implementation detail, not evidence of domain identity — the Entity vs. Value Object decision must be made from the business's point of view first, and the persistence mapping follows it, never the other way around.
- If a Value Object is supposed to have no identity, why do ORMs like JPA/Hibernate often give it a database row and sometimes even a primary key?ORMs need some way to persist and retrieve data, and relational tables traditionally require a primary key for row identification — that's a storage-layer concern, not a domain concern. The idiomatic mapping is to embed the Value Object as columns on the owning Entity's table (e.g., JPA @Embeddable) so it has no independent identity or table of its own; if the ORM forces a separate table, use a composite key of the owning entity's id plus the value's attributes, never a fresh surrogate id, to avoid accidentally granting it identity.
- How does mutability interact with this distinction — can an Entity be immutable, or a Value Object be mutable?Yes to both in principle, but they're atypical. An immutable Entity is legitimate (e.g., a finalized invoice) — it still has identity but never changes state after creation. A 'mutable Value Object' is usually a design smell: if two things that look equal today can independently diverge tomorrow, they were never truly interchangeable, and you likely need identity to tell them apart.
- What goes wrong if a mutable field is included in an Entity's hashCode()?If you insert the Entity into a HashSet/HashMap and then mutate the field used in hashCode(), the object's hash bucket location becomes stale — subsequent contains()/remove() calls compute a different bucket and fail to find the object. The safe pattern is to base an Entity's hashCode() only on the immutable identity field.
A person vs. a dollar bill: a person has one identity for life even as they age and change (Entity); two $10 bills are interchangeable — you don't care which physical bill you get back, only that the value is $10 (Value Object).
saying these in an interview costs you the question
- Says Entities and Value Objects differ only by 'having more fields'
- Implements a Value Object's equals() using reference/identity comparison (==) instead of comparing all attributes
- Gives a Value Object a mutable setter and a surrogate database id 'just in case'
- Bases an Entity's equals()/hashCode() on all fields instead of just the identity field
- Can't explain why two Money(10, USD) instances should be equal even if constructed separately