Creational

Builder

Separate the construction of a complex object from its representation, allowing the same process to create different representations.

Intermediate Pattern #04 of 23

What is the Builder Pattern?

The Builder pattern lets you construct complex objects step by step. It separates the "how to build" (construction process) from the "what to build" (representation), allowing the same construction steps to produce entirely different objects.

It's especially useful when an object has many optional parts, or when you want to prevent a constructor with a dozen parameters ("telescoping constructor" anti-pattern).

Real-world analogy: Ordering a custom pizza. You tell the builder: thin crust, tomato sauce, mozzarella, pepperoni. The same pizza-builder steps (crust → sauce → cheese → toppings) could produce a vegan pizza or a meat lovers — same process, different result.

The Problem It Solves

Imagine building a House object. It can have walls, a roof, windows, a garden, a pool, a garage... some optional, some required. A constructor with all these parameters becomes unmaintainable. Subclassing for every combination creates a class explosion.

The Builder extracts the construction logic into a separate object, letting you build step-by-step and only set what you need.

Participants

Builder (interface)
Declares construction steps common to all builder types (setWalls, setRoof, setDoors).
ConcreteBuilder
Implements the construction steps differently to build different representations. Also has getResult() to retrieve the finished product.
Director
Defines the order in which to call construction steps — it knows what to build, not how. Optional but useful for reusing construction sequences.
Product
The complex object being built. Different ConcreteBuilders produce different unrelated products.

Visual Flow Diagram

Director construct() calls steps in order «interface» Builder buildWalls() buildRoof() / buildDoors() HouseBuilder buildWalls() buildRoof() getResult(): House CastleBuilder buildWalls() buildRoof() getResult(): Castle Product House/Castle calls builder steps

Java Code Example

Java
// Product
public class House {
    private int walls, doors, windows;
    private boolean hasGarage, hasPool;

    // Setters (package-private, only builder sets)
    void setWalls(int w)    { this.walls = w; }
    void setDoors(int d)    { this.doors = d; }
    void setWindows(int w)  { this.windows = w; }
    void setGarage(boolean g) { this.hasGarage = g; }
    void setPool(boolean p)   { this.hasPool = p; }

    public String toString() {
        return "House[walls=" + walls + ", doors=" + doors +
               ", garage=" + hasGarage + ", pool=" + hasPool + "]";
    }
}

// Builder interface
public interface HouseBuilder {
    void buildWalls();
    void buildDoors();
    void buildWindows();
    void buildGarage();
    void buildPool();
    House getResult();
}

// Concrete Builder — standard house
public class StandardHouseBuilder implements HouseBuilder {
    private House house = new House();

    public void buildWalls()   { house.setWalls(4); }
    public void buildDoors()   { house.setDoors(2); }
    public void buildWindows() { house.setWindows(6); }
    public void buildGarage()  { house.setGarage(false); }
    public void buildPool()    { house.setPool(false); }
    public House getResult()   { return house; }
}

// Director — controls construction sequence
public class Director {
    private HouseBuilder builder;

    public Director(HouseBuilder b) { this.builder = b; }

    public void buildMinimalHouse() {
        builder.buildWalls();
        builder.buildDoors();
    }

    public void buildFullHouse() {
        builder.buildWalls();
        builder.buildDoors();
        builder.buildWindows();
        builder.buildGarage();
        builder.buildPool();
    }
}

// --- Modern Java Fluent Builder (most common in practice) ---
public class Pizza {
    private final String size, crust, sauce;
    private final boolean cheese, pepperoni;

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

    public static class Builder {
        private final String size; // required
        private String crust = "thin";
        private String sauce = "tomato";
        private boolean cheese = true, pepperoni = false;

        public Builder(String size) { this.size = size; }
        public Builder crust(String c)   { crust = c; return this; }
        public Builder pepperoni()       { pepperoni = true; return this; }
        public Pizza   build()            { return new Pizza(this); }
    }
}

// Usage
Pizza p = new Pizza.Builder("large")
    .crust("thick")
    .pepperoni()
    .build();

When to Use / Avoid

✓ Use When

  • Object requires many optional parameters (avoid telescoping constructors)
  • Same construction process should create different representations
  • Step-by-step construction makes sense and order matters
  • You need immutable objects with many fields

✕ Avoid When

  • Objects are simple with few fields — just use a constructor
  • No multiple representations are needed
  • Adding many new classes for simple creation is unjustified

Real-World Examples

Pros & Cons

Pros

  • Eliminates telescoping constructors — clean readable code
  • Reuse same construction code for different product types
  • Supports immutable objects — no setters needed after build()
  • Single Responsibility — construction separated from representation

Cons

  • More code — need to create separate Builder class
  • Builder and Product must be kept in sync when adding fields
  • Overkill for simple object creation

How Builder Can Be Broken

⚠ Attack Vectors

  • Partial build & use: Client calls build() before setting required fields — produces an invalid or inconsistent object with missing state
  • Reusing a builder after build(): Client calls build() twice or continues setting fields after the first build(), producing unexpected shared or mutated state
  • Mutable product exposure: If the built object exposes setters, clients bypass the builder entirely and mutate the "immutable" product after construction
  • Director bypassed: Client calls builder steps in wrong order (e.g., roof before walls), violating construction constraints
  • Builder-Product sync: A field added to Product but not to Builder causes silent default values with no compile-time warning

✓ Prevention

  • Validate in build(): Check all required fields before constructing the product — throw IllegalStateException if mandatory fields are missing
  • Invalidate builder after build(): Set a built = true flag; subsequent calls to build() or setters throw an exception
  • Immutable product: Give the product only final fields and no setters — only the builder (inner class) can set them via the private constructor
  • Required fields via constructor: Pass truly required fields to the Builder's constructor, not as optional setters — enforced at compile time
  • Use Lombok @Builder: Automates Builder generation and keeps it in sync with the Product automatically
Java — Break & Fix
// ❌ BREAKING — partial build, missing required field
Pizza p = new Pizza.Builder("large")
    .build(); // sauce is null — invalid pizza!

// ✅ FIX — validate inside build()
public Pizza build() {
    if (sauce == null)
        throw new IllegalStateException("Sauce is required");
    return new Pizza(this);
}

// ❌ BREAKING — reusing builder after build()
Pizza.Builder builder = new Pizza.Builder("large").sauce("tomato");
Pizza p1 = builder.build();
builder.pepperoni(); // mutates already-built p1 state!
Pizza p2 = builder.build(); // surprising result

// ✅ FIX — invalidate builder after first build()
private boolean built = false;
public Pizza build() {
    if (built) throw new IllegalStateException("Builder already used");
    built = true;
    return new Pizza(this);
}

// ✅ FIX — immutable product, only builder sets fields
public class Pizza {
    private final String size, sauce; // final — no setters
    private Pizza(Builder b) { this.size = b.size; this.sauce = b.sauce; }
    // No setters exposed to clients
}

How Other Patterns Relate

Interview Cheat Sheet

  1. What: Separates construction from representation. Use when you need to build a complex object step-by-step, or when an object has many optional parameters.
  2. How: Builder interface with one method per construction step. ConcreteBuilder fills a Product. Optional Director controls the step sequence. Call getResult() when done.
  3. Modern Java: Fluent inner static Builder class (e.g., Lombok @Builder). Each setter returns this for chaining; build() creates the final immutable object.