skip to content

Entities & Value Objects

Entities have identity that persists as their attributes change; value objects are defined entirely by their values and should be immutable. You will learn why pushing behavior into value objects is the cheapest way to make a model expressive.

part ofSoftware design & architectureoverview, primer and where to startread it →
on this pageshow

questions

6

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?

level: juniorimportance: must knowfreq 80%

answer

  1. Identity vs structural equality
  2. equals() compares id (Entity) vs all fields (VO)
  3. VO = interchangeable if equal
  4. surrogate DB id != domain identity

basics

~20 s

An 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 s

Entities 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

for a junior

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.

for a middle

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).

for a senior

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.

for a principal

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

context

open as a page

An unsaved Entity (say, a new `Order` object) doesn't yet have a database-assigned id — it's null until the first save. If equals() is implemented purely by comparing id, what breaks when two such transient, not-yet-persisted Order instances are compared or placed in a hash-based collection, and how do teams typically handle Entity equality before an id exists?

level: middleimportance: must knowfreq 65%

basics

~20 s

If equals() only checks the id and the id is null before saving, then two different unsaved orders both look 'equal' or the equality logic becomes unreliable, which can cause objects to get lost in Sets/Maps once the id is assigned. Teams usually fall back to comparing object references until an id exists, or generate the id in code (like a UUID) before saving so it's never null.

open as a page

How do you decide whether to model a concept — say, a customer's shipping Address — as an Entity or a Value Object, and why might the same real-world concept be modeled differently in two different bounded contexts of the same system?

level: seniorimportance: must knowfreq 70%

basics

~30 s

Ask: does the business need to track this specific instance over time and tell it apart from an identical-looking one, or does it just describe a value that's interchangeable if the details match? An address used just to ship a package is usually a Value Object (only the street/city/zip matters). But if a 'Verified Address' needs its own approval history and audit trail, it becomes an Entity with its own identity.

open as a page

Why should Value Objects be immutable, and what does 'side-effect-free behavior' mean in practice — for example, should a method like `Money.add(Money other)` mutate the receiver or return a new instance?

level: middleimportance: should knowfreq 60%

basics

~20 s

Value Objects should never change after creation. Any 'change' should produce a brand-new object instead of altering the original. So Money.add() should return a new Money with the summed amount, not modify the original — that way nothing else holding a reference to the original gets silently surprised.

open as a page

A system originally identifies `User` entities by email address (using the email as the natural key/identity). Later, the business adds an 'email change' feature. What breaks because of this identity choice, and how does a surrogate key like a UUID fix it?

level: seniorimportance: should knowfreq 55%

basics

~20 s

If a user's identity IS their email, then changing the email means the user 'becomes a different user' as far as the system is concerned — any records referencing the old email (orders, permissions, links) lose their connection. A separate, unchanging ID (like a random UUID) fixes this because the ID never changes even when the email does.

open as a page

A team wants to keep a full history of every state an `Order` entity passed through (e.g., for auditing or event sourcing) by storing immutable snapshots of the Order's data at each transition. Does storing these snapshots turn Order into a Value Object, and how should the snapshot type itself be modeled?

level: principalimportance: nice to knowfreq 30%

basics

~20 s

No — Order is still an Entity because it still has one identity across its whole life. The snapshots are a different, separate thing: each snapshot is a Value Object (immutable, no identity of its own) that just describes 'what the Order looked like at this specific point in time,' tagged with the Order's id and a timestamp.

open as a page