4. Builder
The Builder pattern separates the construction of a complex object from its representation, allowing the same construction process to create different representations. It is ideal when objects have many optional parameters or require validated, multi-step assembly.
Intent and Motivation
Intent: Separate the construction of a complex object from its representation so that the same construction process can create different representations.
Motivation: Telescoping constructors (constructors with 5, 10, 15 parameters) are unreadable and error-prone. A Person with name, email, phone, address, age, and 20 optional fields cannot sanely use overloaded constructors. Builder lets clients set only the fields they need through a fluent API, validates invariants before build(), and keeps the target object immutable once constructed.
Modern fluent APIs — new HttpClient.Builder().timeout(30).retry(3).build() — are Builder pattern in practice.
Structure (UML-like)
┌─────────────┐ uses ┌─────────────┐
│ Director │ ──────────────────► │ Builder │ (interface)
└─────────────┘ ├─────────────┤
│ + buildPartA()│
│ + buildPartB()│
│ + getResult() │
└──────▲──────┘
│ implements
┌──────┴──────┐
│ConcreteBuilder│
├─────────────┤
│ - product │
│ + buildPartA()│
│ + getResult() │
└─────────────┘
Participants:
- Builder — abstract interface for creating parts of the product.
- ConcreteBuilder — implements builder steps and assembles the final product.
- Product — the complex object under construction.
- Director (optional) — defines the order of construction steps.
Java Example
public class Pizza {
private final String size;
private final String crust;
private final boolean cheese;
private final boolean pepperoni;
private Pizza(Builder builder) {
this.size = builder.size;
this.crust = builder.crust;
this.cheese = builder.cheese;
this.pepperoni = builder.pepperoni;
}
public static class Builder {
private String size = "medium";
private String crust = "thin";
private boolean cheese = true;
private boolean pepperoni = false;
public Builder size(String size) {
this.size = size;
return this;
}
public Builder crust(String crust) {
this.crust = crust;
return this;
}
public Builder cheese(boolean cheese) {
this.cheese = cheese;
return this;
}
public Builder pepperoni(boolean pepperoni) {
this.pepperoni = pepperoni;
return this;
}
public Pizza build() {
if (size == null || size.isBlank()) {
throw new IllegalStateException("Size is required");
}
return new Pizza(this);
}
}
@Override
public String toString() {
return size + " " + crust + " cheese=" + cheese + " pepperoni=" + pepperoni;
}
}
// Usage
Pizza pizza = new Pizza.Builder()
.size("large")
.crust("thick")
.pepperoni(true)
.build();
System.out.println(pizza);
JavaScript Example
class HttpRequest {
constructor({ method, url, headers, body, timeout }) {
this.method = method;
this.url = url;
this.headers = headers;
this.body = body;
this.timeout = timeout;
}
}
class HttpRequestBuilder {
constructor() {
this._options = {
method: 'GET',
headers: {},
timeout: 5000
};
}
method(m) { this._options.method = m; return this; }
url(u) { this._options.url = u; return this; }
header(key, value) {
this._options.headers[key] = value;
return this;
}
body(b) { this._options.body = b; return this; }
timeout(ms) { this._options.timeout = ms; return this; }
build() {
if (!this._options.url) {
throw new Error('URL is required');
}
return new HttpRequest(this._options);
}
}
const req = new HttpRequestBuilder()
.method('POST')
.url('https://api.example.com/users')
.header('Content-Type', 'application/json')
.body(JSON.stringify({ name: 'Alice' }))
.timeout(10000)
.build();
Real-World Use Cases
| Framework / System | Usage |
|---|---|
| Java StringBuilder | Classic builder — append parts, then toString() produces the final string. |
Lombok @Builder |
Generates builder boilerplate for Java data classes automatically. |
OkHttp Request.Builder |
Fluent HTTP request construction with validation at build(). |
| gRPC / Protobuf | Message.newBuilder().setField().build() for immutable message objects. |
| React | JSX is a declarative builder for virtual DOM element trees. |
| SQL Query Builders | Knex.js, jOOQ — chain .select().from().where().build() for type-safe queries. |
Pros and Cons
| Pros | Cons |
|---|---|
| Fine control over construction step order | Requires creating a separate Builder class (more code) |
| Fluent, readable API for many optional parameters | Immutability means rebuilding for small changes |
Validates invariants in build() before object exists |
Director layer adds complexity if construction order is rigid |
| Same construction process, different representations | Overkill for simple objects with few fields |
| Produces immutable products safely | Mutable builders must not leak before build() is called |
When to Use vs When NOT to Use
Use when:
- The algorithm for creating a complex object should be independent of the parts and how they are assembled.
- The construction process must allow different representations of the same product.
- The object has many optional fields or requires step-by-step validation.
- You want an immutable product with a mutable construction phase.
Do NOT use when:
- The object is simple with few required fields — a constructor or factory method suffices.
- Subclassing can vary the product effectively (consider Factory Method).
- You need families of related products (use Abstract Factory).
- Language features like named parameters or records with defaults cover your needs (Kotlin data classes, Python dataclasses).
Common Mistakes
- Forgetting validation in
build()— invalid partial state should never become a product. - Returning the mutable builder from
build()— clients could modify state after construction. - Not making the product constructor private — bypasses the builder and its validation.
- Over-engineering simple DTOs — a 3-field object does not need a builder.
- Sharing builder instances across threads — builders are typically not thread-safe.
Related Patterns
- Abstract Factory — creates families of related products; Builder focuses on one complex product’s assembly.
- Composite — Builder is often used to construct Composite tree structures step by step.
- Factory Method — can produce a builder as the factory’s product, or builder can use factory methods for parts.
- Prototype — alternative when copying a pre-built template is simpler than step-by-step construction.
- Fluent Interface — Builder is the most common application of the fluent interface design technique.