Start Learning
Javaneer
Back to stage
Stage 2·Domain-Driven Design Lite

Entities & Value Objects

The two kinds of domain object: entities with identity that changes over time, and immutable value objects defined only by their attributes (Money, DateRange).

15 min readIntermediate
On this page

DDD divides domain objects into two fundamental kinds, and getting the distinction right is the difference between a clean model and a tangle of primitives. An entity has an identity that persists through change; a value object is defined purely by its attributes and is immutable. Most bugs in modeling come from treating a value as an entity, or - far more commonly - not modeling values at all and drowning in raw Strings and BigDecimals.

Entities: identity that outlives change

An entity is something the business tracks as the same thing over time, even as its attributes change. A Transaction in ledger-legacy is an entity: its amount might be corrected, its status moves from pending to settled, but it's still the same transaction, identified by its id.

class Transaction {                        // an ENTITY - has identity
    private final TransactionId id;        // identity, set once, never changes
    private Money amount;                  // attributes CAN change over time
    private TransactionStatus status;

    // equality is by IDENTITY, not attributes
    public boolean equals(Object o) {
        return o instanceof Transaction t && this.id.equals(t.id);
    }
    public int hashCode() { return id.hashCode(); }
}

The defining traits: a stable identity (an id assigned at creation), equality by that identity (two transactions are "the same" iff same id, regardless of current attributes), and a lifecycle (created, modified, archived). Ask: if all its attributes changed, would the business still call it the same thing? If yes, it's an entity.

Value objects: defined by what they are

A value object has no identity - it's entirely described by its attributes, and two with equal attributes are interchangeable. Money, a DateRange, an Address - $50 USD is $50 USD; there's no "which fifty dollars." Value objects are immutable and compared by value:

record Money(BigDecimal amount, Currency currency) {   // a VALUE OBJECT
    Money {                                             // validate invariants at creation
        if (amount.scale() > currency.getDefaultFractionDigits())
            throw new IllegalArgumentException("too many decimal places");
    }
    Money add(Money other) {                            // operations return NEW instances
        requireSameCurrency(other);
        return new Money(amount.add(other.amount), currency);   // no mutation
    }
}
// equality is automatic and by-value for records: Money(50,USD).equals(Money(50,USD)) == true

Java records are the perfect fit: immutable, value-based equals/hashCode for free. Operations don't mutate; they return new instances (like String or LocalDate).

Why value objects matter so much

The most impactful DDD-lite habit is replacing primitives with value objects - the cure for "primitive obsession." Compare:

void transfer(BigDecimal amount, String currency, String fromAccount, String toAccount)  // 🚩 all primitives
void transfer(Money amount, AccountId from, AccountId to)                                 // ✅ typed values

The second version:

  • Can't be called wrong - you can't accidentally swap from and to if they're both raw Strings... but with AccountId types plus clear names, and impossible to pass a currency where an account goes.
  • Centralizes validation - Money's constructor rejects negative or over-precise amounts once, so no caller can construct an invalid one.
  • Carries behavior - money.add(other) enforces currency-matching; scattered BigDecimal arithmetic doesn't.
  • Makes illegal states unrepresentable - a DateRange that guarantees start ≤ end can't express an inverted range.

Prefer value objects; they're cheap and safe

When in doubt, model a concept as an immutable value object rather than a bag of primitives. They're side-effect free, trivially testable, thread-safe, and free of identity bookkeeping. A good heuristic: if you find yourself passing the same two or three primitives around together (amount+currency, lat+lng, start+end), that cluster wants to be a value object.

Banknotes and bank accounts

A banknote is a value object: a $20 bill is interchangeable with any other $20 bill - you don't care which twenty you're handed, only that it's twenty dollars, and you'd never track its lifecycle. A bank account is an entity: it has an account number (identity) that stays the same as the balance rises and falls, transactions post, and the address on file changes - it's the same account throughout, and 'same account' means same number, not same balance. Confusing the two - tracking individual banknotes by serial number, or treating two accounts with equal balances as identical - is where modeling goes wrong.

Entity or value object?

Classify each concept in a ledger system as an entity or a value object, and justify: (1) a Customer, (2) a postal Address, (3) an Invoice, (4) a Money amount, (5) an ISO Currency code. For the value objects, note one benefit of modeling it as a type instead of a primitive.

What's the defining difference between an entity and a value object?

Key takeaways

  • Entities have a stable identity that persists through change; equality is by id, and they have a lifecycle (Transaction, Customer, Invoice).
  • Value objects have no identity - they're immutable and defined entirely by their attributes; equality is by value (Money, Address, DateRange).
  • Java records are ideal for value objects: immutable with value-based equals/hashCode for free; operations return new instances.
  • Replacing primitive clusters with value objects cures 'primitive obsession' - centralizing validation, carrying behavior, and making illegal states unrepresentable.
  • Test: if all attributes changed, would the business call it the same thing? Yes → entity; interchangeable by attributes → value object.
Was this lesson helpful?
Edit this page on GitHub