IMPLEMENTATION QUALITY / OBJECT DESIGN BRIEF

Value objects

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.

IntermediatePhase 07 / Topic 4 of 8ResponsibilitiesCollaborationsExtensibility
01

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.

Banknotes

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.

02

When to use it

  • Money, quantities, measurements, and units.
  • Identifiers and formatted strings (email, phone, SKU).
  • Ranges, coordinates, and addresses.
03

Where it shows up in interviews

Primitive obsession cure

Recognize it when: doubles for money, strings for everything.

  • Design Splitwise
  • Design a payment system
  • Design a parking fee calculator
04

Where it is used in real software

Joda-Money and java.time

Money and time types encapsulate currency and time rules.

DDD

Value objects are a core building block alongside entities and aggregates.

Stripe amounts

Stripe represents money as integer minor units plus currency to avoid floating point errors.

05

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

How it works, step by step

  1. 1
    Find primitives with rules

    Amounts, emails, IDs, ranges.

  2. 2
    Create an immutable type

    Record, data class, frozen object.

  3. 3
    Validate and normalize in the constructor

    Lowercase email; non-negative cents.

  4. 4
    Add domain behavior

    add, split, overlaps, format.

  5. 5
    Implement value equality

    Records and data classes do this automatically.

07

Splitting a bill with Money

Split $100.00 among 3 people

Step 1 / 2
ApproachResultProblem
double 100 / 333.333333 eachFractions of a cent, sum mismatch
Money(10000 cents).split(3)3334, 3333, 3333Exact; 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.

08

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 equality
09

Complexity and performance

CreationO(1) + validation

Small objects.

EqualityO(fields)

Compare values.

10

Trade-offs

Type safety vs verbosity

More small types, but far fewer rule violations and clearer signatures.

Persistence mapping

ORMs need embeddable types or converters to store value objects.

11

Variants and related techniques

Branded / opaque types

TypeScript brands (string & { brand: 'UserId' }) for IDs without runtime cost.

Embedded values

JPA @Embeddable stores value objects inside the entity's table.

12

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.

13

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.

14

Practice problems

ProblemDifficultyWhat it trains
Money with split and allocate by ratioMediumRemainders.
TimeSlot value object with overlap checksEasyRange logic.