skip to content

What is the Builder pattern in Java, and what problem does it solve?

level: juniorimportance: must knowfreq 78%

answer

  1. Telescoping constructors vs JavaBeans vs Builder
  2. Each setter returns this → fluent chain
  3. build() validates and returns immutable object
  4. Required in builder ctor, optional as chained methods
  5. JDK: StringBuilder, Stream.Builder, Locale.Builder

basics

~20 s

Builder is a way to construct an object step by step using chained method calls, then a final build() call. It avoids constructors with lots of confusing parameters, and the result is usually an immutable object.

solid answer

~40 s

The Builder pattern separates how a complex object is constructed from the object itself. Instead of a constructor taking many parameters, you create a Builder, call a fluent setter for each value (each returns the builder so calls chain), and finish with build(), which produces the final, usually immutable, object. It directly solves the telescoping-constructor problem, where you'd otherwise need many overloaded constructors for every combination of optional fields, and it's far safer than the JavaBeans setter approach because the object can't be observed in a half-initialized state and can be made immutable. Builders make each argument self-documenting (you see the method name at the call site), let you validate or default values in build(), and naturally express required-vs-optional parameters. Classic JDK examples are StringBuilder, Stream.Builder, and Locale.Builder.

go deeper

for a junior

Can describe Builder as chained setters ending in build() and knows it avoids messy multi-argument constructors.

for a middle

Explains the telescoping-constructor and JavaBeans alternatives, implements a correct fluent builder with required-in-constructor / optional-chained, and validates in build().

for a senior

Articulates immutability and thread-safety benefits, the never-half-constructed guarantee, the boilerplate/allocation trade-off, and when NOT to use it (few params, or a simple record).

for a principal

Frames Builder against records and language evolution, discusses API ergonomics and validation strategy at scale, and weighs it in a team's construction conventions.

## The problem When a class has many fields, especially many *optional* ones, you have two traditional ways to construct it, and both are bad. **1. Telescoping constructors.** You write one constructor per combination of parameters: ```java public Pizza(int size) { ... } public Pizza(int size, boolean cheese) { ... } public Pizza(int size, boolean cheese, boolean pepperoni) { ... } ``` This explodes combinatorially, and at the call site `new Pizza(12, true, false, true)` is unreadable — you can't tell which boolean means what, and you can easily swap two same-typed arguments without the compiler noticing. **2. The JavaBeans pattern.** Use a no-arg constructor plus setters: `var p = new Pizza(); p.setSize(12); p.setCheese(true);`. This reads better, but the object is **mutable** (it can't be made immutable), and it exists in a **partially-constructed, inconsistent state** between the setter calls — a real hazard in multithreaded code, and it makes class invariants impossible to enforce at construction. ## The Builder solution **Builder** gives you the readability of JavaBeans plus the safety/immutability of a constructor. You define a separate `Builder` helper (usually a `static` nested class) that holds the in-progress values. Each configuration method sets one field and **returns the builder itself** (`return this;`) so calls *chain* — this is called a **fluent interface**. A final `build()` method passes the collected values to the target class's (typically private) constructor and returns the finished object. ```java public final class Pizza { private final int size; // required private final boolean cheese; // optional private final boolean pepperoni; private Pizza(Builder b) { // private: only the builder constructs this.size = b.size; this.cheese = b.cheese; this.pepperoni = b.pepperoni; } public static class Builder { private final int size; // required → builder constructor arg private boolean cheese = false; // optional → sensible default private boolean pepperoni = false; public Builder(int size) { this.size = size; } public Builder cheese(boolean v) { this.cheese = v; return this; } public Builder pepperoni(boolean v) { this.pepperoni = v; return this; } public Pizza build() { if (size <= 0) throw new IllegalArgumentException("size must be positive"); return new Pizza(this); } } } Pizza p = new Pizza.Builder(12).cheese(true).pepperoni(true).build(); ``` ## Why each piece matters - **`return this`** is what makes the calls chain into one fluent statement. - **Required vs optional:** put required values in the builder's constructor (so they can't be forgotten); expose optional ones as chained methods with defaults. - **`build()` is the validation point:** check invariants there and fail fast with an exception, so an invalid object is *never* created. - **Immutability:** because the target's fields are `final` and its constructor is `private`, once `build()` returns there is no way to mutate the object — it's safe to share across threads. - **Self-documenting:** `.cheese(true)` at the call site says exactly what the argument means, unlike a bare positional `true`. ## Trade-offs The cost is **boilerplate** — you maintain a parallel Builder class — and a small **allocation** for the builder object per construction. So Builder is overkill for a class with two or three parameters; it shines when there are many parameters, especially optional ones, or when you want immutability with readable construction. Java **records** reduce the need for builders for simple immutable data, but a record's canonical constructor is still positional, so a builder is still useful when a record has many components. ## JDK examples - **`StringBuilder`** — `new StringBuilder().append("a").append(1).toString()`. The chained `append` returning the builder is exactly the fluent style (here `toString()` plays the role of `build()`). - **`Stream.Builder`** — `Stream.<String>builder().add("a").add("b").build()`. - **`Locale.Builder`** — `new Locale.Builder().setLanguage("en").setRegion("US").build()`.

  • How does Builder express the difference between required and optional parameters?
    Required parameters go in the builder's own constructor (so you can't construct a builder without them), while optional ones are exposed as chained methods that default to a sensible value if never called.
  • Why is StringBuilder considered an example of the Builder pattern?
    Its append methods each return the StringBuilder, so calls chain fluently, and toString() acts as the terminal build() that produces the final String.

saying these in an interview costs you the question

  • Saying Builder is the same as JavaBeans setters — it isn't; Builder yields an immutable, never-half-built object.
  • Claiming Builder is always better than a constructor — for 2-3 params it's just boilerplate.
  • Forgetting that fluent chaining requires each method to return the builder (this).
  • Putting validation in the target constructor only and forgetting build() is the natural fail-fast point.

context