One-line Definition
Encapsulates object construction step by step, allowing the same construction process to create different representations.
Problem
Suppose you have a class with many optional fields. You might end up with a dozen constructor overloads (the "telescoping constructor" anti-pattern) or a single constructor with 10 parameters where clients pass null for things they don't need. This makes instantiation error-prone and hard to read. The Builder Pattern solves this by moving object construction into a dedicated builder object.
Real-world Analogy
Think of ordering a custom burger. You don't hand the cashier a list of 12 ingredients in exact order — you say "add lettuce," "add cheese," "no onions," "extra sauce." Each step is named and optional. The builder assembles your burger one topping at a time, and only at the end do you get the finished product.
Structure
- Client
- Associates a Builder with a Director (optional) or directly interacts with the Builder to assemble the object.
- Product
- The complex object being built.
- Builder
- Interface or nested static class with setter-like methods that return
thisfor chaining.
- Interface or nested static class with setter-like methods that return
- Director (optional)
- Orchestrates the build steps in a predefined order.
Diagram
classDiagram
class Client {
}
class Order {
-String orderId
-String customerId
-String status
}
class Builder {
+orderId(id) Builder
+customerId(id) Builder
+status(status) Builder
+build() Order
}
Client --> Builder : configures
Builder ..> Order : creates
Order +-- Builder : nested inside
Code Walkthrough
Notice that Order is instantiated by its nested Builder. The client names every parameter it passes in, making the code self-documenting.
public class Order {
private final String orderId;
private final String customerId;
private final String date;
private final String status;
private final double subtotal;
private final double tax;
private final String shippingCity;
private final boolean isPriority;
private Order(Builder builder) {
this.orderId = builder.orderId;
this.customerId = builder.customerId;
this.date = builder.date;
this.status = builder.status;
this.subtotal = builder.subtotal;
this.tax = builder.tax;
this.shippingCity = builder.shippingCity;
this.isPriority = builder.isPriority;
}
public static class Builder {
private final String orderId; // Required
private final String customerId; // Required
private String date;
private String status = "PENDING";
private double subtotal;
private double tax;
private String shippingCity;
private boolean isPriority = false;
public Builder(String orderId, String customerId) {
this.orderId = orderId;
this.customerId = customerId;
}
public Builder date(String date) { this.date = date; return this; }
public Builder subtotal(double subtotal) { this.subtotal = subtotal; return this; }
public Builder tax(double tax) { this.tax = tax; return this; }
public Builder shippingCity(String city) { this.shippingCity = city; return this; }
public Builder priority(boolean priority) { this.isPriority = priority; return this; }
public Order build() {
return new Order(this);
}
}
}
// Usage
Order order = new Order.Builder("ORD-123", "CUST-456")
.date("2024-01-15")
.subtotal(1500.0)
.tax(270.0)
.shippingCity("Mumbai")
.priority(true)
.build();
Bad vs Good
Bad Approach
Problems
- Impossible to tell what each positional argument means.
- Adding a new optional field requires a new constructor overload.
- Easy to swap two arguments of the same type (like city and pincode).
public class Order {
public Order(String orderId, String customerId, String date,
String status, String currency, double subtotal,
double tax, double total, String city,
String pincode, boolean isPriority, String shippingMode) {
// assign all fields...
}
}
// Caller: which argument is the city? which is the pincode?
Order order = new Order("ORD-123", "CUST-456", "2024-01-15",
"PENDING", "INR", 1500.0, 270.0, 1770.0, "Mumbai",
"400001", true, "Express");
Better Approach
Improvements
- Every parameter is named — no positional ambiguity.
- New fields are added by extending the builder, keeping object creation flexible.
Order order = new Order.Builder("ORD-123", "CUST-456")
.date("2024-01-15")
.subtotal(1500.0)
.tax(270.0)
.shippingCity("Mumbai")
.priority(true)
.build();
Pros vs Cons
| Pros | Cons |
|---|---|
| Self-documenting object creation | More boilerplate code to write and maintain |
| Handles optional parameters gracefully | Overkill for simple objects with few fields |
| Makes creating immutable objects easy | Duplicated field declarations between Builder and Product |
| Allows validation before instantiation | Requires creating an extra object in memory (the builder) |
| Avoids the telescoping constructor anti-pattern |
When to Use
- An object has more than 4 parameters, especially optional ones.
- You want to construct immutable objects safely.
- The construction process involves multiple complex steps.
- You want to avoid confusing constructor overloads.
When Not to Use
- The object has 2–3 required fields and no optional fields.
- The object is highly mutable and setters work just fine.
- The added boilerplate code outweighs the readability benefits.
Real-world Examples
java.lang.StringBuilder(andStringBuffer)java.util.stream.Stream.Builderjava.net.http.HttpRequest.newBuilder()- Lombok's
@Builderannotation
Key Takeaway
The Builder Pattern eliminates unreadable constructors by letting you name every parameter and set only what you need. Use it when constructing complex, parameter-heavy objects, but stick to a simple constructor if the object only has a couple of fields.