The Builder pattern separates the construction of a complex object from its representation, allowing the same construction process to create different representations. It shines when an object requires many configuration options, optional parameters, or a specific construction sequence — making the code readable and maintainable where telescoping constructors would fail.
Consider building an HTTP request object. You need a URL, an HTTP method, headers, query parameters, a request body, a timeout value, and retry settings. A constructor with all these parameters is unreadable and error-prone. You cannot tell which argument is which without checking the signature, and adding new options means changing every call site. Optional parameters make this even messier.
The Builder pattern provides a step-by-step API for constructing the object. Each method sets one property and returns the builder itself, enabling method chaining. When all desired options are configured, a final build() call produces the immutable result. The object is always in a valid, complete state after construction.
Separate construction from representation. The builder encapsulates the construction logic so the client code reads like a declaration of what the object should look like, not how it gets assembled internally.
This example builds an HttpRequest object with a fluent API, supporting method, URL, headers, body, timeout, and retries.
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class HttpRequest {
private final String url;
private final String method;
private final Map<String, String> headers;
private final String body;
private final int timeoutMs;
private final int maxRetries;
// Private constructor — only the Builder can create instances
private HttpRequest(Builder builder) {
this.url = builder.url;
this.method = builder.method;
this.headers = Collections.unmodifiableMap(builder.headers);
this.body = builder.body;
this.timeoutMs = builder.timeoutMs;
this.maxRetries = builder.maxRetries;
}
public String getUrl() { return url; }
public String getMethod() { return method; }
public Map<String, String> getHeaders() { return headers; }
public String getBody() { return body; }
public int getTimeoutMs() { return timeoutMs; }
public int getMaxRetries() { return maxRetries; }
@Override
public String toString() {
return method + " " + url
+ " | headers=" + headers.size()
+ " | timeout=" + timeoutMs + "ms"
+ " | retries=" + maxRetries;
}
// Static inner Builder class
public static class Builder {
private final String url; // Required
private String method = "GET"; // Default
private Map<String, String> headers = new HashMap<>();
private String body = null;
private int timeoutMs = 30000; // Default 30s
private int maxRetries = 0;
public Builder(String url) {
if (url == null || url.isEmpty()) {
throw new IllegalArgumentException("URL is required");
}
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 timeoutMs) {
this.timeoutMs = timeoutMs;
return this;
}
public Builder retries(int maxRetries) {
this.maxRetries = maxRetries;
return this;
}
public HttpRequest build() {
return new HttpRequest(this);
}
}
}
// Usage
public class Application {
public static void main(String[] args) {
HttpRequest request = new HttpRequest.Builder("https://api.example.com/users")
.method("POST")
.header("Content-Type", "application/json")
.header("Authorization", "Bearer token123")
.body("{\"name\": \"Alice\", \"email\": \"alice@example.com\"}")
.timeout(5000)
.retries(3)
.build();
System.out.println(request);
// Simple GET request with defaults
HttpRequest getRequest = new HttpRequest.Builder("https://api.example.com/health")
.build();
System.out.println(getRequest);
}
}
The same HTTP request builder in Python, using method chaining and an immutable result.
from dataclasses import dataclass, field
from typing import Optional
@dataclass(frozen=True)
class HttpRequest:
url: str
method: str = "GET"
headers: dict = field(default_factory=dict)
body: Optional[str] = None
timeout_ms: int = 30000
max_retries: int = 0
def __str__(self):
return (
f"{self.method} {self.url}"
f" | headers={len(self.headers)}"
f" | timeout={self.timeout_ms}ms"
f" | retries={self.max_retries}"
)
class HttpRequestBuilder:
def __init__(self, url: str):
if not url:
raise ValueError("URL is required")
self._url = url
self._method = "GET"
self._headers = {}
self._body = None
self._timeout_ms = 30000
self._max_retries = 0
def method(self, method: str) -> "HttpRequestBuilder":
self._method = method
return self
def header(self, key: str, value: str) -> "HttpRequestBuilder":
self._headers[key] = value
return self
def body(self, body: str) -> "HttpRequestBuilder":
self._body = body
return self
def timeout(self, timeout_ms: int) -> "HttpRequestBuilder":
self._timeout_ms = timeout_ms
return self
def retries(self, max_retries: int) -> "HttpRequestBuilder":
self._max_retries = max_retries
return self
def build(self) -> HttpRequest:
return HttpRequest(
url=self._url,
method=self._method,
headers=dict(self._headers),
body=self._body,
timeout_ms=self._timeout_ms,
max_retries=self._max_retries,
)
# Usage
if __name__ == "__main__":
request = (
HttpRequestBuilder("https://api.example.com/users")
.method("POST")
.header("Content-Type", "application/json")
.header("Authorization", "Bearer token123")
.body('{"name": "Alice", "email": "alice@example.com"}')
.timeout(5000)
.retries(3)
.build()
)
print(request)
# Simple GET with defaults
get_request = HttpRequestBuilder("https://api.example.com/health").build()
print(get_request)
append() calls before producing the final String.requests.Request objects can be prepared step-by-step before sending via a Session.