Builder Pattern in Java
A pizza order has a size (required), and then maybe extra cheese, maybe a stuffed crust, maybe toppings, maybe a delivery note. With constructors alone you end up writing new PizzaOrder("LARGE", true, false, null, "Ring the bell"), and nobody can tell what true, false, null mean. The Builder pattern replaces that with named, chainable steps: .size("LARGE").extraCheese().note("Ring the bell").build().
The Problem: Telescoping Constructors
new PizzaOrder("LARGE", true, false, null, "Ring the bell");
new PizzaOrder("SMALL", false, true, List.of("Corn"), null);Overloaded constructors for every combination ("telescoping" constructors) multiply quickly, and long argument lists of the same type are easy to get in the wrong order without any compile error.
A Fluent Builder
import java.util.ArrayList;
import java.util.List;
public class Pizzas {
static final class PizzaOrder {
private final String size;
private final boolean extraCheese;
private final List<String> toppings;
private final String note;
private PizzaOrder(Builder b) {
this.size = b.size;
this.extraCheese = b.extraCheese;
this.toppings = List.copyOf(b.toppings);
this.note = b.note;
}
static Builder builder(String size) { return new Builder(size); }
@Override
public String toString() {
return size + (extraCheese ? " + cheese" : "") + " " + toppings
+ (note == null ? "" : " [" + note + "]");
}
static final class Builder {
private final String size;
private boolean extraCheese;
private final List<String> toppings = new ArrayList<>();
private String note;
private Builder(String size) { this.size = size; }
Builder extraCheese() { extraCheese = true; return this; }
Builder topping(String t) { toppings.add(t); return this; }
Builder note(String n) { note = n; return this; }
PizzaOrder build() {
if (toppings.size() > 5) {
throw new IllegalStateException("At most 5 toppings");
}
return new PizzaOrder(this);
}
}
}
public static void main(String[] args) {
PizzaOrder navika = PizzaOrder.builder("LARGE")
.extraCheese()
.topping("Paneer").topping("Capsicum")
.note("Ring the bell")
.build();
PizzaOrder advitha =
PizzaOrder.builder("SMALL").topping("Corn").build();
System.out.println(navika);
System.out.println(advitha);
}
}LARGE + cheese [Paneer, Capsicum] [Ring the bell]
SMALL [Corn]How it works
PizzaOrderis immutable, with a private constructor that takes aBuilder.Builderis a static nested class (see "Static Nested Class in Java"). It holds the settings while you choose them. The required size goes in its constructor.- Each setter returns
this, so calls chain:.extraCheese().topping(...). build()validates the whole combination once, then creates the finished, immutable order.- Unset options keep their defaults: Advitha's order has no cheese and no note.
When to Use a Builder
| Situation | Choose |
|---|---|
| Two or three required fields, nothing optional | A constructor, or a record |
| Many optional settings | A builder |
| Several parameters of the same type | A builder, so each value is named |
| Validation across several fields | A builder's build() method |
The JDK uses builders for exactly these cases: StringBuilder, HttpRequest.newBuilder(), HttpClient.newBuilder(), Locale.Builder and Stream.builder().
Common Mistakes
Mutable results. The point is to build a finished, safe object; keep the product immutable.
Validating in each setter only. Rules that involve several fields belong in build().
Reusing a builder after build() and expecting independent objects. Copy lists in the product's constructor, as List.copyOf does above.
Interview Corner
What is the builder pattern and when would you use it?
The builder pattern separates constructing a complex object from its representation: a builder object collects settings through named, chainable methods, then build() validates them and creates the final, usually immutable, object. It's used when a class has many optional parameters or several parameters of the same type, where telescoping constructors become unreadable and error-prone.
Practice
- Easy: Add a
stuffedCrust()option to the builder and use it for Srihitha's order. - Harder: Write a builder for an
EmailMessagewith requiredtoandsubject, optionalcc(a list) andbody, and validate inbuild()thattocontains an@.
Key Takeaways
Was this tutorial helpful?