skip to content

Give examples of the Builder pattern in the JDK and explain how each realizes the pattern.

level: middleimportance: should knowfreq 52%

answer

  1. Pattern shape: mutable accumulator + terminal product call
  2. StringBuilder.append... .toString() (toString = build)
  3. Stream.builder().add().build()
  4. Locale.Builder validates BCP-47 → IllformedLocaleException
  5. HttpRequest.Builder / HttpClient.Builder (Java 11+)

basics

~10 s

StringBuilder, Stream.Builder, and Locale.Builder are JDK examples. Each lets you add values with chained calls and then produces a final object — toString() for StringBuilder, build() for the other two.

solid answer

~40 s

The clearest JDK builders are StringBuilder, Stream.Builder, and Locale.Builder. StringBuilder accumulates characters via chained append calls (each returns the StringBuilder) and toString() acts as the terminal step that yields the final String. Stream.Builder lets you add elements with add (or accept), chaining, and build() produces the finished Stream. Locale.Builder configures language, region, script, and variant through fluent setters and build() returns an immutable Locale, validating well-formedness along the way. The common shape is: a mutable helper that collects state through fluent methods, plus one terminal operation that produces a finished — usually immutable — product. Other fluent JDK APIs in this spirit include Calendar.Builder, the java.net.http HttpRequest.Builder and HttpClient.Builder, and Stream.collect with Collectors, though some of these are 'fluent configuration' rather than the textbook GoF Builder.

go deeper

for a junior

Names StringBuilder as a builder and knows append chains while toString() gives the final String.

for a middle

Lists several JDK builders (StringBuilder, Stream.Builder, Locale.Builder, HttpRequest.Builder) and maps each onto the accumulator + terminal-product shape.

for a senior

Distinguishes true builders from builder-like reductions (collect/Collectors), notes validation and single-use semantics, and explains StringBuilder's performance rationale.

for a principal

Relates the JDK's fluent-builder idiom to the original GoF Builder (with Director) and discusses how the JDK uses builders to expose immutable, validated configuration objects in modern APIs.

## What to look for To recognize a Builder in a library, look for the two-part signature of the pattern: a **mutable accumulator with fluent (chaining) methods**, finished by a **single terminal call that returns the product**. The terminal call doesn't have to be named `build()` — `toString()` plays that role for `StringBuilder`. ## StringBuilder (and StringBuffer) ```java String s = new StringBuilder() .append("user=").append(name) .append(", id=").append(id) .toString(); ``` - **Mutable accumulator:** the `StringBuilder` holds a growable character buffer. - **Fluent methods:** every `append` (and `insert`, `reverse`, …) returns `this`, so calls chain. - **Terminal step:** `toString()` produces the final immutable `String` — this is the `build()` of the pattern. - This is *the* canonical, most-used builder in everyday Java, and it also exists for a performance reason: building a String by `+` in a loop creates many throwaway String objects, whereas a single mutable `StringBuilder` appends in place. (`StringBuffer` is the older thread-safe variant.) ## Stream.Builder ```java Stream<String> stream = Stream.<String>builder() .add("a").add("b").add("c") .build(); ``` - **Mutable accumulator:** collects elements. - **Fluent methods:** `add` returns the builder (it also implements `Consumer` via `accept`). - **Terminal step:** `build()` produces the `Stream`. After `build()` the builder is in a 'built' state and adding more throws `IllegalStateException` — a common builder safeguard. ## Locale.Builder ```java Locale loc = new Locale.Builder() .setLanguage("en").setRegion("US").setScript("Latn") .build(); ``` - **Fluent setters** for the parts of a locale. - **Validation:** the setters/`build()` validate BCP-47 well-formedness, throwing `IllformedLocaleException` for bad input — the validation-in-construction trait of the pattern. - **Immutable product:** `Locale` is immutable. ## Other JDK fluent/builder APIs - **`Calendar.Builder`** — `new Calendar.Builder().setDate(2026, 5, 20).build()`. - **`java.net.http` (Java 11+):** `HttpRequest.newBuilder(uri).GET().header("k","v").build()` and `HttpClient.newBuilder()...build()` are textbook builders returning immutable objects. - **`DateTimeFormatterBuilder`** — accumulates formatting rules and `toFormatter()` is the terminal step. - **Streams' `collect` with `Collectors`** uses a *mutable reduction* that is builder-like in spirit (a mutable container is filled then finished), though it's a reduction API, not the GoF Builder. ## A note on naming vs. the GoF pattern The Gang of Four 'Builder' also includes a separate *Director* that orchestrates the building steps. Most modern Java 'builders' drop the Director and are really the **fluent-builder idiom** popularized by *Effective Java*: the client itself drives the steps. When an interviewer asks for 'Builder in the JDK', the fluent examples above are what they expect.

  • Why does StringBuilder exist when you can already concatenate Strings with +?
    String is immutable, so repeated + in a loop allocates a new String each time; StringBuilder appends into one mutable buffer and produces the final String once with toString(), avoiding the garbage and quadratic cost.
  • What happens if you call add() on a Stream.Builder after build()?
    It throws IllegalStateException — the builder transitions to a built state and refuses further mutation, a common safety trait of single-use builders.

saying these in an interview costs you the question

  • Calling String itself a builder — String is the immutable product; StringBuilder is the builder.
  • Saying StringBuilder isn't a builder because its terminal step is toString() not build() — the name is irrelevant; the role is what matters.
  • Confusing Stream.Builder (constructs a stream) with Stream.collect (reduces a stream).
  • Claiming every fluent API is the GoF Builder — many are fluent configuration without a Director.

context