80. Builder Pattern
The Builder pattern solves the telescoping-constructor problem by providing a fluent API to assemble complex objects step-by-step; in Java 16+, records and their wither pattern reduce the need for Builders on simple immutable types.
Java 21 (core language)Explanation
When a class has many optional parameters, the telescoping constructor anti-pattern (one constructor per combination) becomes unmanageable. The Builder pattern: a static nested Builder class collects parameters via setter-style methods (returning this for fluency), then creates the target object in a final build() method.
Benefits: client code reads like a DSL; parameters can be validated in build(); the target object can be immutable (all fields final); optional parameters have defaults. Drawbacks: verbose โ requires a parallel class with all the same fields.
Modern alternative: for simple immutable data, a record with a wither pattern or a compact constructor covers most cases without a separate builder class.
Example
public class BuilderPattern {
// Immutable class with Builder
static final class HttpRequest {
private final String method;
private final String url;
private final java.util.Map<String, String> headers;
private final String body;
private final int timeoutMs;
private HttpRequest(Builder b) {
this.method = b.method;
this.url = b.url;
this.headers = java.util.Collections.unmodifiableMap(b.headers);
this.body = b.body;
this.timeoutMs = b.timeoutMs;
}
@Override public String toString() {
return method + " " + url + " (timeout=" + timeoutMs + "ms, headers=" + headers + ")";
}
static class Builder {
// Required
private final String method;
private final String url;
// Optional with defaults
private java.util.Map<String, String> headers = new java.util.HashMap<>();
private String body = null;
private int timeoutMs = 5000;
Builder(String method, String url) {
this.method = method;
this.url = url;
}
Builder header(String name, String value) { headers.put(name, value); return this; }
Builder body(String body) { this.body = body; return this; }
Builder timeoutMs(int ms) { this.timeoutMs = ms; return this; }
HttpRequest build() {
if (method == null || url == null)
throw new IllegalStateException("method and url are required");
return new HttpRequest(this);
}
}
}
public static void main(String[] args) {
// Fluent builder
HttpRequest get = new HttpRequest.Builder("GET", "https://api.example.com/users")
.header("Authorization", "Bearer token123")
.header("Accept", "application/json")
.timeoutMs(10_000)
.build();
System.out.println(get);
HttpRequest post = new HttpRequest.Builder("POST", "https://api.example.com/users")
.header("Content-Type", "application/json")
.body("{\"name\": \"Alice\"}")
.build();
System.out.println(post);
// Record wither pattern โ simpler alternative for records
record Point(int x, int y) {
Point withX(int newX) { return new Point(newX, y); }
Point withY(int newY) { return new Point(x, newY); }
}
var p = new Point(1, 2).withX(5).withY(10);
System.out.println(p); // Point[x=5, y=10]
}
}Sources
- https://www.pearson.com/en-us/subject-catalog/p/effective-java/P200000000138 โ Effective Java, Item 2
- https://refactoring.guru/design-patterns/builder โ Builder pattern reference