Builder Pattern
IntermediateThe Builder pattern constructs complex objects step-by-step with a fluent API, solving the telescoping constructor problem.
Overview
The Builder pattern separates the construction of a complex object from its representation. When a class has many optional parameters, constructors become unwieldy (telescoping constructors). Builder provides a fluent API where you set only the fields you need and call build() to create the immutable object. Lombok's @Builder annotation generates the builder automatically.
Classic Builder Implementation
A static inner Builder class mirrors the target class fields. Each setter returns this (the Builder) for method chaining. The build() method validates and constructs the target object.
Make the target class constructor private so it can only be created via the Builder.
public final class DatabaseConfig {
private final String host;
private final int port;
private final String database;
private final int maxConnections;
private final Duration timeout;
private final boolean ssl;
private DatabaseConfig(Builder b) {
this.host = b.host;
this.port = b.port;
this.database = b.database;
this.maxConnections = b.maxConnections;
this.timeout = b.timeout;
this.ssl = b.ssl;
}
public static Builder builder() { return new Builder(); }
public static class Builder {
private String host = "localhost";
private int port = 5432;
private String database;
private int maxConnections = 10;
private Duration timeout = Duration.ofSeconds(30);
private boolean ssl = false;
public Builder host(String host) { this.host = host; return this; }
public Builder port(int port) { this.port = port; return this; }
public Builder database(String db) { this.database = db; return this; }
public Builder maxConnections(int n) { this.maxConnections = n; return this; }
public Builder timeout(Duration t) { this.timeout = t; return this; }
public Builder ssl(boolean ssl) { this.ssl = ssl; return this; }
public DatabaseConfig build() {
if (database == null) throw new IllegalStateException("database required");
return new DatabaseConfig(this);
}
}
}
// Usage
DatabaseConfig config = DatabaseConfig.builder()
.host("db.example.com")
.database("production")
.maxConnections(50)
.ssl(true)
.build();Lombok @Builder
Lombok's @Builder annotation generates the builder automatically at compile time. @Builder.Default sets field defaults. @Singular handles collection fields — adds a single-element adder method.
This eliminates the boilerplate while retaining all the benefits of the Builder pattern.
import lombok.Builder;
import lombok.Singular;
import lombok.Value;
@Value // generates immutable class with all-args constructor
@Builder // generates builder
public class HttpRequest {
String method; // required
String url; // required
@Builder.Default
Duration timeout = Duration.ofSeconds(30);
@Singular
List<String> headers; // adds header(String) and headers(List<String>)
@Singular("queryParam")
Map<String, String> queryParams;
}
// Usage
HttpRequest req = HttpRequest.builder()
.method("GET")
.url("https://api.example.com/users")
.header("Authorization: Bearer token123")
.queryParam("page", "1")
.queryParam("size", "20")
.build();Record + Builder Combination
Java records are immutable and generate all-args constructors, but lack a fluent builder. You can add a nested Builder class to a record, or use a compact constructor for validation. For Java 16+ records used as data transfer objects, a static factory + copy method (withX) pattern is common.
public record Person(String name, int age, String email) {
// Compact constructor for validation
public Person {
if (name == null || name.isBlank())
throw new IllegalArgumentException("Name required");
if (age < 0 || age > 150)
throw new IllegalArgumentException("Invalid age");
}
// Wither methods — return new record with one field changed
public Person withName(String newName) {
return new Person(newName, age, email);
}
public Person withAge(int newAge) {
return new Person(name, newAge, email);
}
// Static builder
public static Builder builder() { return new Builder(); }
public static class Builder {
private String name; private int age; private String email;
public Builder name(String n) { this.name = n; return this; }
public Builder age(int a) { this.age = a; return this; }
public Builder email(String e) { this.email = e; return this; }
public Person build() { return new Person(name, age, email); }
}
}Key Points to Remember
- Builder solves the telescoping constructor problem when a class has many optional fields.
- Each setter returns this (the Builder) for fluent method chaining.
- Make the target constructor private — only the Builder should create instances.
- Lombok @Builder generates the entire builder at compile time.
- @Builder.Default sets field defaults; @Singular adds single-element add methods for collections.
Practice Builder Pattern in the Playground
Run and modify code directly in your browser - no setup needed.
Interview Questions
Sign in to ask AriaWhat problem does the Builder pattern solve?
What is the telescoping constructor anti-pattern?
How is Builder different from Factory pattern?
How does Lombok @Builder work under the hood?
How do you add validation to a Builder's build() method?
Ask Aria about Builder Pattern
Your personal AI tutor — ask anything about this concept