skip to content

When should you choose a Builder over constructors, static factories, or records — and when is it the wrong tool?

level: seniorimportance: should knowfreq 46%

answer

  1. Heuristic: >~4 params, many optional → Builder
  2. Few params → constructor / static factory / record
  3. Static factory = named constructor, can cache/return subtype
  4. Record = free immutable data but positional ctor
  5. Record + builder is a common combo; build() ~ a factory

basics

~20 s

Use a Builder when a class has many parameters, especially optional ones, and you want readable construction and an immutable result. For just a few parameters, a constructor, static factory, or a record is simpler and a Builder is needless boilerplate.

solid answer

~50 s

The deciding factor is the number and optionality of parameters. With a handful of required parameters, a constructor or a named static factory (of/valueOf/getInstance) is clearest. Once a class has many parameters — and especially many optional ones — telescoping constructors become unreadable and JavaBeans setters sacrifice immutability and allow half-built objects, so the Builder wins: it's self-documenting, expresses required-vs-optional cleanly, validates in build(), and yields an immutable object. Records cover the common case of simple immutable data with auto-generated accessors/equals/hashCode, but a record's canonical constructor is still positional, so for a record with many components a builder layered on top restores readable construction. Avoid the Builder when parameters are few (the boilerplate and per-construction allocation aren't worth it), when you genuinely need mutability, or when a simple static factory already reads well. Effective Java's guidance: consider a builder when a constructor or static factory would have more than a handful of parameters, especially if many are optional or of the same type.

go deeper

for a junior

Knows a Builder helps when there are many parameters but may not weigh it against factories or records.

for a middle

Chooses Builder vs constructor vs static factory based on parameter count and optionality, and recognizes records for simple immutable data.

for a senior

Applies the >~4-params heuristic, articulates the immutability/readability trade-offs, and combines record + builder appropriately.

for a principal

Sets construction conventions for a codebase, balances boilerplate/allocation against ergonomics, and reasons about API evolution (adding optional fields without breaking callers) when picking among the tools.

## The four construction tools 1. **Constructor** — `new Foo(a, b)`. Simple and direct, but positional: hard to read with many args, can't have two constructors with the same parameter types, and can't give arguments meaningful names at the call site. 2. **Static factory method** — `Foo.of(a, b)`, `Foo.valueOf(...)`, `Foo.getInstance()`. Like a constructor but with a *name* (so multiple 'constructors' with the same signature are possible), it can return a cached instance or a subtype, and decouples the caller from the concrete class. Still positional, so it doesn't solve the many-optional-params problem. 3. **Builder** — fluent, step-by-step construction ending in `build()`. Solves readability + immutability for many/optional parameters; costs boilerplate and a small allocation. 4. **Record** — `record Point(int x, int y){}`. Compiler generates the canonical constructor, accessors, `equals`/`hashCode`/`toString`. Immutable by design, minimal boilerplate — but the canonical constructor is *positional*, so many components are still hard to read at the call site. ## The decision The single most useful heuristic (from *Effective Java*): **consider a Builder when a constructor or static factory would have more than roughly four parameters, especially if many are optional or several share the same type.** Reasoning: - **Few required params →** constructor or static factory. A Builder here is pure ceremony. - **Many params, especially optional →** Builder. Telescoping constructors explode combinatorially and read terribly; JavaBeans setters give up immutability and allow inconsistent intermediate states. The Builder keeps it readable *and* immutable. - **Same-typed adjacent params (two `boolean`s, two `int`s) →** lean toward Builder (or static factory with a name) because positional calls are easy to transpose silently. - **Simple immutable data →** a **record** is usually the right answer; you don't need a builder at all. - **Record with many (optional) components →** keep the record, but add a builder that ends in returning the record, for readable construction. ## When the Builder is the *wrong* tool - **Few parameters.** The parallel builder class and the extra allocation aren't justified; a constructor/factory/record is clearer. - **You need a mutable object.** Builder's whole value is producing an immutable, fully-formed object; if the object is meant to change over its lifetime, a Builder adds nothing. - **A named static factory already reads well** and there are no optional combinations to manage. - **Performance-critical hot paths** where the per-construction builder allocation matters (rare, but worth noting). ## How they combine, not compete These tools aren't mutually exclusive. A common, idiomatic combination is a **record + a builder**: the record gives you free immutability/equals/hashCode/toString, and the builder gives readable construction; `build()` calls the record's canonical constructor. Likewise a builder's `build()` is itself a kind of factory method, and a class may offer *both* a simple static factory (for the common case) and a builder (for the elaborate case). ## Quick contrast table - Constructor: positional, simplest, no names, no immutability help beyond final fields. - Static factory: positional but *named*, can cache/return subtype. - Builder: fluent, named args, required/optional clear, immutable result, more boilerplate. - Record: immutable + free members, positional canonical constructor. ## Bottom line Reach for a Builder when readability and immutability of a *many-parameter* construction are the priority; reach for a record for simple immutable data; reach for a constructor or named static factory for a small, fixed set of arguments. Don't pay the Builder's boilerplate tax unless the parameter count earns it.

  • What advantages does a static factory method have over a Builder, and vice versa?
    A static factory is named, can cache or return a subtype, and is cheaper for a small fixed argument list; a Builder wins when there are many — especially optional — parameters by giving readable, self-documenting, immutable construction. They can also be combined (build() is a factory).
  • If Java has records, when would you still write a builder?
    When the record has many components, especially optional ones — the record's canonical constructor is positional and unreadable at scale, so a builder layered over it restores fluent, named, validated construction.

saying these in an interview costs you the question

  • Using a Builder for a 2-parameter class — boilerplate with no benefit.
  • Believing records make builders obsolete — records have positional constructors, so many-component records still benefit from a builder.
  • Treating constructor / static factory / Builder / record as mutually exclusive rather than combinable.
  • Choosing JavaBeans setters for an object that should be immutable just to get readable construction.

context