Builder Pattern
Builder Pattern
The Builder pattern separates the construction of a complex object from its representation, allowing the same construction process to produce different results. In modern Java it is the canonical solution for building immutable value objects that have many optional fields — the situations where telescoping constructors become unreadable and setters prevent immutability.
Why Not Just Use Constructors or Setters?
Imagine a HttpRequest that has a required URL, a required method, and eight optional fields (headers, body, timeout, follow-redirects, auth, proxy, cache policy, retry count). Two common but flawed approaches are:
- Telescoping constructors — you write one constructor per combination. With eight optional fields you end up with dozens, and call sites like
new HttpRequest(url, method, null, null, 30, true, null, null, 3)are impossible to read. - JavaBean style (mutable setters) — the object starts in an inconsistent state, it cannot be made
final/immutable, and thread-safety requires external synchronisation.
The Builder pattern solves both problems: a fluent API makes call sites self-documenting, and the target object is assembled only once — at build() time — so it can be fully immutable.
The Classic Static Inner Builder
The idiomatic Java form places a public static final class Builder inside the target class. The target class has a private constructor that accepts only the builder.
A call site reads like a sentence:
Builder constructor so the compiler enforces them. Optional parameters get default values and fluent setters. This is clearer than making every field optional and validating completeness in build().
Validation in build()
The build() method is the right place for cross-field invariants. Single-field validation (null checks, range checks) belongs in each fluent setter so the error is raised close to the bad call. For instance:
IllegalArgumentException in the setter the moment you see bad data. If you defer all validation to build() the stack trace points there, not at the actual bad call, and debugging takes longer.
Copy Builders — Safe Immutable Updates
Because the target object is immutable you cannot modify it after construction. A copy builder (also called a withX pattern) lets you derive a new object from an existing one while changing only specific fields — the same idea as Java records' with expressions:
Builder vs Java Records
Java 16+ records give you compact immutable data carriers for simple cases. Use a Builder when:
- There are many optional fields (records have a single canonical constructor — all fields required).
- You need cross-field validation logic in
build(). - The object is part of a fluent API (query builders, HTTP clients, test fixtures).
- You want to support copy builders cleanly.
For a 3-field data holder with no optional fields and no validation, a record is simpler and preferable.
Lombok @Builder — When to Reach for It
In production codebases Lombok's @Builder annotation generates the inner Builder at compile time, eliminating boilerplate. The trade-off: the generated builder has no required-field enforcement and no custom validation unless you add @Builder.ObtainVia and manual build() overrides. Use it for straightforward DTO / value objects; hand-roll when you need strict invariants.
Real-World Usage in the JDK and Ecosystem
The pattern is everywhere in modern Java:
StringBuilder/StringJoiner— accumulate parts, produce aString.HttpClient.newBuilder()/HttpRequest.newBuilder()— JDK 11+ HTTP client.ProcessBuilder— configure and launch OS processes.- Spring's
MockMvcRequestBuilders, Hibernate'sCriteriaBuilder, Guava'sImmutableList.builder().
Summary
The Builder pattern is the professional answer to complex object construction in Java. By placing required fields in the Builder constructor, providing fluent optional-field setters, centralising validation in build(), and keeping the target class fully immutable, you get readable call sites, compile-time safety, and objects that are trivially safe to share across threads. The copy-builder extension makes immutable updates ergonomic. In the next lesson we move to behavioural patterns, starting with Strategy.