Entity vs Value Object in DDD
Fast recall
Quick revision
Core answer
Orders with the same items are not the same Order if they have different IDs. A Value Object has no identity — it's defined entirely by its attributes; two Money(50, USD) instances are completely interchangeable. Entities are mutable through controlled domain methods; Value Objects are immutable and replaced wholesale. The distinction drives equality, mutability, JPA mapping (@Entity vs @Embeddable), and whether the concept gets its own database table.When identity matters vs when attribute equality is enough; how this distinction reshapes equality, mutability, and persistence.
Slapping `@Entity` and a `Long id` on every class — making `Money`, `Address`, and `DateRange` into entities with surrogate keys for no domain reason.
Show concrete value-object benefits (thread safety, no aliasing bugs, behavior on the type), records as VO implementation, and the JPA mapping consequences.
Interview-ready guide
Interview answer
Why the distinction matters
Most developers default to Long id + @Entity on everything. Money, Address, DateRange all become entities with surrogate keys for no domain reason — unnecessary tables, pointless joins, aliasing bugs.
DDD's distinction:
- Entity: continuity through time, identity matters. Bank account stays 'your account' through deposits/withdrawals
- Value Object: a measurement, description, quantity. A price, address, date range. Two with same attributes are completely interchangeable — 'this $50' = 'that $50'
Getting it right cascades into equality, mutability, persistence, threading.
Side by side
Identity: ID vs none. Equality: by ID vs by attributes. Mutability: controlled mutation vs immutable, replaced wholesale. Java: class with private state vs record / final class. JPA: @Entity (own table) vs @Embeddable (columns in owning row). Thread safety: needs sync vs inherently safe. Lifecycle: independent vs lives within entity. Examples: Order, Customer, Account vs Money, Address, DateRange, EmailAddress.
Value Objects in Java — records + behavior
Money as record(BigDecimal amount, Currency currency). Compact constructor validates invariants. plus() returns new Money; throws if currency mismatched — invalid arithmetic impossible by design. times(qty), isGreaterThan(other) — behavior on the type.
Records auto-generate equals/hashCode/toString based on components. Attribute-based equality automatic. No setters — every operation returns new Money.
Entities — identity and controlled state
Order with OrderId identity, CustomerId, items, status, total. Constructor validates 'order needs items'. cancel() is a named business method, throws if shipped. Equals based on id only. State changes only through named methods, never raw setters.
OrderId itself is a value object — strongly typed, can't pass CustomerId where OrderId expected.
JPA mapping
Order is @Entity with @Id, owns its row. Money is @Embeddable; its columns live inside the order's row (total_amount, total_currency). No separate table, no join.
Wrong: @Entity Money with @GeneratedValue Long id. Now every order has FK to money table; every line item creates two extra rows; queries explode in joins. None represents real domain identity.
The identity test
'If I change one of its attributes, is it still the same thing?'
- Yes → Entity. Bank account with different balance is still your account
- No → VO. Different amount of money is different money. Different street is different address
Another test: 'Are two of them with identical attributes interchangeable?' Yes = VO; No = Entity.
Borderline: Address usually VO. For real estate, Property is entity containing Address VO. Person almost always entity. Color usually VO unless paint company managing SKUs.
Common mistakes
- @Entity on everything — phantom identity, unnecessary tables
- Mutable value objects — aliasing bugs
- Equality by reference for VOs — forgetting to override equals/hashCode
- Equality by attributes for entities — two Customers with same name aren't the same
- Long primitive IDs everywhere — transfer(Long, Long, Long) unreadable; strongly typed VOs make APIs self-documenting
- Service-style behavior — MoneyUtils.add(a, b) instead of a.plus(b)
- Returning mutable collections from entities — callers mutate internal state
Recommendation
Default to Value Objects for measurements, descriptions, quantities, and IDs — use Java records with validation in compact constructors and behavior as methods on the type. Use Entities only when identity persists through state changes (Order, Customer, Account). Map VOs as @Embeddable in JPA — columns inside the owning entity's row, not separate tables. Strongly typed IDs (OrderId, CustomerId) as VOs prevent whole categories of bugs at compile time. The identity test ('still the same thing if attributes change?') is the deciding factor.
Entity vs Value Object in DDD
Direct Answer
An Entity has a unique identity that persists through state changes — two Orders with the same items are not the same Order if they have different IDs. A Value Object has no identity — it's defined entirely by its attributes; two Money(50, USD) instances are completely interchangeable. Entities are mutable through controlled domain methods; Value Objects are immutable and replaced wholesale. The distinction drives equality, mutability, JPA mapping (@Entity vs @Embeddable), and whether the concept gets its own database table.
Common Mistakes & Pitfalls
Slapping @Entity and a Long id on every class — making Money, Address, and DateRange into entities with surrogate keys for no domain reason.
Check your understanding
Answer these without looking back at the article.
- Why are Value Objects inherently thread-safe?
- How does using `OrderId` as a value object instead of `Long` improve API safety?
- When would you treat `Address` as an entity instead of a value object?
- Show how to validate a `Money` value object in its constructor and via behavior methods.
Related interview prep categories
1Want a guided curriculum order?
These modules are also sequenced in our flagship backend interview track — open it when you prefer an ordered path instead of browsing by topic.