java 128 lines · 3 tabs

Immutable Value Object with a Fluent Builder and build()-Time Validation in Java

Shared by codesnips Jul 2026
3 tabs
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Currency;
import java.util.Objects;

public final class Money {
    private final BigDecimal amount;
    private final Currency currency;

    private Money(BigDecimal amount, Currency currency) {
        int scale = currency.getDefaultFractionDigits();
        this.amount = amount.setScale(scale, RoundingMode.UNNECESSARY);
        this.currency = currency;
    }

    static Money create(BigDecimal amount, Currency currency) {
        return new Money(amount, currency);
    }

    public static MoneyBuilder builder() {
        return new MoneyBuilder();
    }

    public BigDecimal amount() {
        return amount;
    }

    public Currency currency() {
        return currency;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Money)) return false;
        Money other = (Money) o;
        return amount.equals(other.amount) && currency.equals(other.currency);
    }

    @Override
    public int hashCode() {
        return Objects.hash(amount, currency);
    }

    @Override
    public String toString() {
        return amount.toPlainString() + " " + currency.getCurrencyCode();
    }
}
3 files · java Explain with highlit

This snippet shows the classic combination for modeling a domain value in Java: an immutable object plus a fluent builder that validates everything in one place at build() time. The Money value object is the target type, and the MoneyBuilder usage tab exercises the API the way calling code actually reads.

The Money value object is a final class with final fields and no setters, so once constructed an instance can never change. That immutability is the reason such objects are safe to share across threads and cache freely — there is no observable state transition to guard. The private constructor forces all creation through the builder, which keeps the invariants in a single choke point. Note the defensive handling of Currency and amount: the class normalizes the scale to the currency's fraction digits so two equal amounts always compare and hash equally, which is what makes equals and hashCode reliable for use in sets and map keys.

The MoneyBuilder is a static nested class returning this from each setter, giving the fluent chain. Crucially, none of the setters throw; they merely record intent. All validation is deferred to build(), where require checks accumulate into a single IllegalStateException listing every problem at once instead of failing on the first bad field. This is friendlier for callers than a scatter of guard clauses and mirrors how bean-validation frameworks report multiple violations together.

The pattern solves the telescoping-constructor problem: instead of many overloaded constructors with ambiguous positional arguments, named setters make each value explicit and optional fields easy. The trade-off is more boilerplate and a transient mutable builder, so it pays off mainly for objects with several fields or non-trivial invariants — a two-field struct rarely needs it.

The MoneyBuilder usage tab demonstrates both the happy path and how the aggregated error message surfaces. A subtle pitfall worth noting: because the builder is mutable and not thread-safe, one builder instance should not be shared across threads, and build() should be treated as producing an independent snapshot. Reusing a builder to produce several objects is fine as long as it stays confined to one thread.


Related snips

Share this code

Here's the card — post it anywhere.

Immutable Value Object with a Fluent Builder and build()-Time Validation in Java — share card
Link copied