When should you choose a Builder over constructors, static factories, or records — and when is it the wrong tool?
answer
- Heuristic: >~4 params, many optional → Builder
- Few params → constructor / static factory / record
- Static factory = named constructor, can cache/return subtype
- Record = free immutable data but positional ctor
- Record + builder is a common combo; build() ~ a factory
basics
~20 sUse 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 sThe 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
Knows a Builder helps when there are many parameters but may not weigh it against factories or records.
Chooses Builder vs constructor vs static factory based on parameter count and optionality, and recognizes records for simple immutable data.
Applies the >~4-params heuristic, articulates the immutability/readability trade-offs, and combines record + builder appropriately.
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.