Overview
A value object is a small immutable object defined entirely by its values rather than an identity: Money(100, USD), Email('[email protected]'), Coordinates(12.9, 77.6), DateRange. Two value objects with the same values are interchangeable and equal. They validate themselves on creation and contain behavior related to their values (Money.add, DateRange.overlaps).
Value objects cure 'primitive obsession', where doubles, strings, and ints represent domain concepts and the rules for them are scattered. Replacing primitives with value objects makes invalid values impossible, puts behavior next to data, and makes method signatures self-documenting.
Any 20-dollar bill is as good as any other; you care about the value, not which physical bill it is. A person, however, has identity: two people named Ana are not interchangeable.
When to use it
- Money, quantities, measurements, and units.
- Identifiers and formatted strings (email, phone, SKU).
- Ranges, coordinates, and addresses.
Where it shows up in interviews
Recognize it when: doubles for money, strings for everything.
- Design Splitwise
- Design a payment system
- Design a parking fee calculator
Where it is used in real software
Money and time types encapsulate currency and time rules.
Value objects are a core building block alongside entities and aggregates.
Stripe represents money as integer minor units plus currency to avoid floating point errors.
Key terms
- Value equality
- Equal if all values are equal.
- Identity equality
- Equal only if the same entity (entities).
- Self-validation
- Invalid values cannot be constructed.
- Primitive obsession
- Using primitives for domain concepts.
- Minor units
- Cents: integers avoid float rounding.
How it works, step by step
- 1Find primitives with rules
Amounts, emails, IDs, ranges.
- 2Create an immutable type
Record, data class, frozen object.
- 3Validate and normalize in the constructor
Lowercase email; non-negative cents.
- 4Add domain behavior
add, split, overlaps, format.
- 5Implement value equality
Records and data classes do this automatically.
Splitting a bill with Money
Split $100.00 among 3 people
| Approach | Result | Problem |
|---|---|---|
| double 100 / 3 | 33.333333 each | Fractions of a cent, sum mismatch |
| Money(10000 cents).split(3) | 3334, 3333, 3333 | Exact; remainder distributed |
NOWApproach: double 100 / 3 | Result: 33.333333 each | Problem: Fractions of a cent, sum mismatch
Encapsulating money rules in a value object prevents rounding bugs everywhere it is used.
Implementation
public record Money(long cents, String currency) implements Comparable<Money> { public Money { if (cents < 0) throw new IllegalArgumentException("Negative amount"); if (currency == null || currency.length() != 3) throw new IllegalArgumentException("ISO currency required"); } public static Money of(String amount, String currency) { return new Money(new BigDecimal(amount).movePointRight(2).longValueExact(), currency); } public Money plus(Money o) { requireSame(o); return new Money(cents + o.cents, currency); } public List<Money> split(int parts) { long base = cents / parts, remainder = cents % parts; List<Money> shares = new ArrayList<>(); for (int i = 0; i < parts; i++) shares.add(new Money(base + (i < remainder ? 1 : 0), currency)); return shares; } public int compareTo(Money o) { requireSame(o); return Long.compare(cents, o.cents); } private void requireSame(Money o) { if (!currency.equals(o.currency)) throw new IllegalArgumentException("Currency mismatch"); }} Money.of("100.00", "USD").split(3); // [33.34, 33.33, 33.33]new Money(500, "USD").equals(new Money(500, "USD")); // true: value equalityComplexity and performance
Small objects.
Compare values.
Trade-offs
More small types, but far fewer rule violations and clearer signatures.
ORMs need embeddable types or converters to store value objects.
Variants and related techniques
TypeScript brands (string & { brand: 'UserId' }) for IDs without runtime cost.
JPA @Embeddable stores value objects inside the entity's table.
Common mistakes
- Floating point for money.
Fix: Use integer minor units or BigDecimal.
- Mutable value objects.
Fix: Values must be immutable to be safely shared and compared.
- Giving value objects IDs.
Fix: If identity matters, it is an entity.
Interview questions
Entity vs value object?
An entity has a stable identity and lifecycle (Account #42). A value object has no identity; it is defined by its values, is immutable, and is replaced rather than modified (Money, Address).
How would you represent money?
An immutable Money value object with integer minor units (cents) or BigDecimal plus an ISO currency code, validating on creation and providing arithmetic, comparison, and splitting that handles remainders.
Practice problems
| Problem | Difficulty | What it trains |
|---|---|---|
| Money with split and allocate by ratio | Medium | Remainders. |
| TimeSlot value object with overlap checks | Easy | Range logic. |