The Builder pattern separates the construction of a complex object from its representation, allowing the same construction process to create different representations. It is ideal when objects have many optional parameters or require validated, multi-step assembly.

Intent and Motivation

Intent: Separate the construction of a complex object from its representation so that the same construction process can create different representations.

Motivation: Telescoping constructors (constructors with 5, 10, 15 parameters) are unreadable and error-prone. A Person with name, email, phone, address, age, and 20 optional fields cannot sanely use overloaded constructors. Builder lets clients set only the fields they need through a fluent API, validates invariants before build(), and keeps the target object immutable once constructed.

Modern fluent APIs — new HttpClient.Builder().timeout(30).retry(3).build() — are Builder pattern in practice.

Structure (UML-like)

  ┌─────────────┐        uses         ┌─────────────┐
│   Director  │ ──────────────────► │   Builder   │ (interface)
└─────────────┘                     ├─────────────┤
                                    │ + buildPartA()│
                                    │ + buildPartB()│
                                    │ + getResult() │
                                    └──────▲──────┘
                                           │ implements
                                    ┌──────┴──────┐
                                    │ConcreteBuilder│
                                    ├─────────────┤
                                    │ - product    │
                                    │ + buildPartA()│
                                    │ + getResult() │
                                    └─────────────┘
  

Participants:

  • Builder — abstract interface for creating parts of the product.
  • ConcreteBuilder — implements builder steps and assembles the final product.
  • Product — the complex object under construction.
  • Director (optional) — defines the order of construction steps.

Java Example

  public class Pizza {
    private final String size;
    private final String crust;
    private final boolean cheese;
    private final boolean pepperoni;

    private Pizza(Builder builder) {
        this.size = builder.size;
        this.crust = builder.crust;
        this.cheese = builder.cheese;
        this.pepperoni = builder.pepperoni;
    }

    public static class Builder {
        private String size = "medium";
        private String crust = "thin";
        private boolean cheese = true;
        private boolean pepperoni = false;

        public Builder size(String size) {
            this.size = size;
            return this;
        }

        public Builder crust(String crust) {
            this.crust = crust;
            return this;
        }

        public Builder cheese(boolean cheese) {
            this.cheese = cheese;
            return this;
        }

        public Builder pepperoni(boolean pepperoni) {
            this.pepperoni = pepperoni;
            return this;
        }

        public Pizza build() {
            if (size == null || size.isBlank()) {
                throw new IllegalStateException("Size is required");
            }
            return new Pizza(this);
        }
    }

    @Override
    public String toString() {
        return size + " " + crust + " cheese=" + cheese + " pepperoni=" + pepperoni;
    }
}

// Usage
Pizza pizza = new Pizza.Builder()
    .size("large")
    .crust("thick")
    .pepperoni(true)
    .build();
System.out.println(pizza);
  

JavaScript Example

  class HttpRequest {
  constructor({ method, url, headers, body, timeout }) {
    this.method = method;
    this.url = url;
    this.headers = headers;
    this.body = body;
    this.timeout = timeout;
  }
}

class HttpRequestBuilder {
  constructor() {
    this._options = {
      method: 'GET',
      headers: {},
      timeout: 5000
    };
  }

  method(m) { this._options.method = m; return this; }
  url(u) { this._options.url = u; return this; }
  header(key, value) {
    this._options.headers[key] = value;
    return this;
  }
  body(b) { this._options.body = b; return this; }
  timeout(ms) { this._options.timeout = ms; return this; }

  build() {
    if (!this._options.url) {
      throw new Error('URL is required');
    }
    return new HttpRequest(this._options);
  }
}

const req = new HttpRequestBuilder()
  .method('POST')
  .url('https://api.example.com/users')
  .header('Content-Type', 'application/json')
  .body(JSON.stringify({ name: 'Alice' }))
  .timeout(10000)
  .build();
  

Real-World Use Cases

Framework / System Usage
Java StringBuilder Classic builder — append parts, then toString() produces the final string.
Lombok @Builder Generates builder boilerplate for Java data classes automatically.
OkHttp Request.Builder Fluent HTTP request construction with validation at build().
gRPC / Protobuf Message.newBuilder().setField().build() for immutable message objects.
React JSX is a declarative builder for virtual DOM element trees.
SQL Query Builders Knex.js, jOOQ — chain .select().from().where().build() for type-safe queries.

Pros and Cons

Pros Cons
Fine control over construction step order Requires creating a separate Builder class (more code)
Fluent, readable API for many optional parameters Immutability means rebuilding for small changes
Validates invariants in build() before object exists Director layer adds complexity if construction order is rigid
Same construction process, different representations Overkill for simple objects with few fields
Produces immutable products safely Mutable builders must not leak before build() is called

When to Use vs When NOT to Use

Use when:

  • The algorithm for creating a complex object should be independent of the parts and how they are assembled.
  • The construction process must allow different representations of the same product.
  • The object has many optional fields or requires step-by-step validation.
  • You want an immutable product with a mutable construction phase.

Do NOT use when:

  • The object is simple with few required fields — a constructor or factory method suffices.
  • Subclassing can vary the product effectively (consider Factory Method).
  • You need families of related products (use Abstract Factory).
  • Language features like named parameters or records with defaults cover your needs (Kotlin data classes, Python dataclasses).

Common Mistakes

  1. Forgetting validation in build() — invalid partial state should never become a product.
  2. Returning the mutable builder from build() — clients could modify state after construction.
  3. Not making the product constructor private — bypasses the builder and its validation.
  4. Over-engineering simple DTOs — a 3-field object does not need a builder.
  5. Sharing builder instances across threads — builders are typically not thread-safe.
  • Abstract Factory — creates families of related products; Builder focuses on one complex product’s assembly.
  • Composite — Builder is often used to construct Composite tree structures step by step.
  • Factory Method — can produce a builder as the factory’s product, or builder can use factory methods for parts.
  • Prototype — alternative when copying a pre-built template is simpler than step-by-step construction.
  • Fluent Interface — Builder is the most common application of the fluent interface design technique.