The Builder pattern decouples object construction from object representation. Instead of constructors with many parameters, a builder accumulates state through a series of method calls and produces the final object at the end. Combined with fluent return types, the result is readable, type-safe construction.
Three situations:
A constructor with 5+ parameters becomes hard to read at the call site. With many optional parameters, the constructor combinatorics explode. Builders solve this:
Order order = Order.builder()
.id("abc")
.amount(99.99)
.priority(Priority.HIGH)
.deliveryDate(LocalDate.of(2026, 5, 1))
.giftWrap(true)
.build();
Each setter is named; the call is self-documenting. Adding a new field doesn't break callers.
For immutable objects (records, value classes), the Builder pattern lets you construct incrementally without making the result mutable. The builder is mutable; the built object is not.
Builders can validate at build(), after all fields are set. Constructors validate per-field; complex inter-field validation is awkward in constructors.
A fluent API has methods that return this, allowing chaining:
public OrderBuilder amount(double amount) {
this.amount = amount;
return this;
}
Combined with the Builder pattern, you get the chain shown above. The same idea applies elsewhere: query builders, configuration objects, mock setup.
mockServer.expect(POST, "/api/orders")
.andRespond(withStatus(201).body(json));
Fluent style works when the methods naturally compose. It's awkward when method order matters or when state transitions need to be explicit.
@Builder
public class Order {
private final String id;
private final BigDecimal amount;
private final Priority priority;
}
Generates the builder at compile time. Less code, less to maintain.
public class Order {
public static Builder builder() { return new Builder(); }
public static class Builder { /* ... */ }
}
Standard Java. More verbose but no annotation processor dependency.
public Order(String id, BigDecimal amount) { ... }
public Order(String id, BigDecimal amount, Priority p) { ... }
public Order(String id, BigDecimal amount, Priority p, LocalDate d) { ... }
Works for 2-3 parameters; doesn't scale.
build() should validate.