Skip to main content

Builder

Builder assembles a complex object step by step, separating construction from representation. It tames long parameter lists and enables immutability at the cost of extra boilerplate.

Type
Creational
When to Use
Many Optional Parameters, Immutable Objects, Step By Step Assembly, Multiple Representations

The Builder pattern separates the construction of a complex object from its final representation, so the same step-by-step process can yield different results. It targets a familiar pain point: constructors with long, error-prone parameter lists (the "telescoping constructor" problem) and objects that require many optional fields or careful assembly ordering.

How It Works

A Builder exposes methods for configuring parts of a product, then a terminal build() method that returns the finished object. The classic GoF form adds a Director that encapsulates a specific construction sequence, calling builder steps in order; the same Director can drive different concrete builders to produce different representations. In modern practice the Director is often omitted, and the builder offers a fluent, chainable API instead.

Because the product is only assembled at build(), the pattern naturally supports immutability: fields are gathered on the builder and passed to the product's constructor all at once, after which the product never changes.

When to Use It

Use Builder when an object has many fields — especially optional ones — when construction must happen in steps, or when you want to produce several representations from one process. It shines for configuration objects, query builders, test-data factories, and immutable domain entities. A fluent builder also dramatically improves call-site readability compared with positional arguments.

Do not use it for objects with two or three simple fields; a constructor or a record is simpler. Builders add boilerplate that only pays off above a certain complexity threshold.

Trade-offs

Builders make complex construction readable and self-documenting, enable immutable objects, and let you validate invariants in build() before the object exists. They isolate assembly logic and allow varying representations.

The cost is duplication: each product needs a parallel builder, and adding a field means touching both. Naive builders can also produce partially configured objects if build() is called early without required fields, so validation matters. Some languages mitigate the boilerplate with code generation (for example, Lombok in Java) or named/default arguments, which can make a hand-written builder unnecessary.

Related Patterns

Builder is a creational sibling of Abstract Factory and Factory Method, but it focuses on one complex object assembled over multiple calls rather than producing products in a single call. It frequently presents a Fluent Interface. A Composite is a common product of a builder, since trees are awkward to build in one constructor.

Example

An HTTP client request is built fluently: Request.builder().url("/users").method(POST).header("Authorization", token).body(json).timeout(30).build(). Only url is required; everything else is optional. The build() method validates the URL and returns an immutable Request. The same builder API could feed a Director that constructs a standardized "retryable request" by always adding retry headers.