The Builder Pattern
The Builder Pattern
The Builder pattern separates the construction of a complex object from its representation. Instead of forcing callers through a constructor with many parameters, you build the object step by step through a dedicated builder. The result is readable, safe, and easy to extend.
The Problem: Telescoping Constructors
Classes with many optional fields tempt you to write overload after overload. This is the telescoping constructor anti-pattern.
public class HttpRequest {
private final String url;
private final String method;
private final Map<String, String> headers;
private final String body;
private final int timeout;
private final boolean followRedirects;
public HttpRequest(String url, String method, Map<String, String> headers,
String body, int timeout, boolean followRedirects) {
this.url = url;
this.method = method;
this.headers = headers;
this.body = body;
this.timeout = timeout;
this.followRedirects = followRedirects;
}
}
Callers must remember the exact order of six parameters and supply values for options they do not care about. With N optional fields you would need 2^N overloads. This is unreadable and error-prone.
Building Step by Step
The Builder moves each option into a named method that returns the builder itself, enabling fluent chaining.
+-----------------------------+
| HttpRequest.Builder |
| method(): Builder |
| header(): Builder |
| body(): Builder |
| timeout(): Builder |
| build(): HttpRequest |
+-----------------------------+
|
| method().header().body()
| (each returns Builder)
|
> build()
+-----------------------------+
| HttpRequest (immutable) |
| final fields, no setters |
+-----------------------------+
The builder accumulates values; build() freezes them into an immutable product.
The Builder Class
The builder is usually a static nested class holding the same fields with sensible defaults.
public class HttpRequest {
private final String url;
private final String method;
private final Map<String, String> headers;
private final String body;
private final int timeout;
private final boolean followRedirects;
private HttpRequest(Builder builder) {
this.url = builder.url;
this.method = builder.method;
this.headers = builder.headers;
this.body = builder.body;
this.timeout = builder.timeout;
this.followRedirects = builder.followRedirects;
}
public static class Builder {
private final String url; // required
private String method = "GET"; // optional with default
private Map<String, String> headers = new HashMap<>();
private String body = "";
private int timeout = 30000;
private boolean followRedirects = true;
public Builder(String url) {
this.url = url;
}
public Builder method(String method) {
this.method = method;
return this;
}
public Builder header(String key, String value) {
this.headers.put(key, value);
return this;
}
public Builder body(String body) {
this.body = body;
return this;
}
public Builder timeout(int timeout) {
this.timeout = timeout;
return this;
}
public HttpRequest build() {
return new HttpRequest(this);
}
}
}
The product constructor is private and copies values from the builder, so every field can be final. Only the url is mandatory; the rest have defaults.
Using the Builder
Usage becomes self-documenting, and the IDE autocompletes only valid options.
HttpRequest request = new HttpRequest.Builder("https://api.example.com")
.method("POST")
.header("Content-Type", "application/json")
.body("name:test")
.timeout(5000)
.build();
// request is immutable and safe to share
System.out.println(request);
Every setter returns the same Builder, so calls chain in any order. Because the product is immutable, it is safe to share across threads.
Real-World Use Cases
Java ships builders everywhere: StringBuilder, Stream.Builder, ProcessBuilder, and java.net.http.HttpRequest. Libraries such as OkHttp, Retrofit, and the MongoDB Java driver use builders for configuration. Any object with many optional parameters is a good candidate.
Key Points
- Builder separates object construction from representation and gives clean code.
- Named methods replace long parameter lists; every option is readable and optional.
- The inner static Builder holds defaults and returns this from each setter.
- The final build() method produces the immutable product object.
- Why not telescoping constructors: exponential overloads, unclear ordering, unreadable calls.