John Roest

The Builder Design Pattern

Wed Jun 04 2025

The Builder Design Pattern

Introduction

The Builder pattern is a creational design pattern used to construct complex objects step by step. It provides a clear and flexible approach to object construction, especially when an object has many optional fields or configurations.

Instead of relying on a single constructor with many parameters, the Builder pattern allows each part of an object to be set individually through a chain of method calls. Once all the required parts are set, the build() method finalizes the object creation.

This pattern is particularly useful when:

  • An object requires numerous parameters for construction
  • Many of those parameters are optional
  • There are multiple representations or configurations of the same object
  • You want to enforce immutability and thread-safety once the object is built

Motivation

Imagine a scenario where you need to construct a House object. A house may contain a variety of optional components: walls, a roof, a garage, a swimming pool, a garden, and solar panels.

A naive constructor approach might look like this:

public class House {
    public House(String walls, String roof, boolean hasGarage, boolean hasSwimmingPool, boolean hasGarden, boolean hasSolarPanels) {
        // initialize fields
    }
}

Creating an instance using this constructor:

House house = new House("brick", "tile", true, false, true, false);

This code is hard to read and maintain. It is not immediately clear what each boolean flag stands for, and changes to the class require changes to the constructor and every place it's used.

Structure of the Builder Pattern

The Builder pattern separates the construction of a complex object from its representation. It provides a fluent interface that makes object creation readable and maintainable.

Participants

  • Product: The object being built (House)
  • Builder: Defines the methods to set each part of the product
  • ConcreteBuilder: Implements the builder interface and returns the final object
  • Director (optional): Orchestrates the construction steps

Implementation Example

Product Class and Builder

public class House {
    private final String walls;
    private final String roof;
    private final boolean hasGarage;
    private final boolean hasSwimmingPool;
    private final boolean hasGarden;
    private final boolean hasSolarPanels;

    private House(Builder builder) {
        this.walls = builder.walls;
        this.roof = builder.roof;
        this.hasGarage = builder.hasGarage;
        this.hasSwimmingPool = builder.hasSwimmingPool;
        this.hasGarden = builder.hasGarden;
        this.hasSolarPanels = builder.hasSolarPanels;
    }

    public static class Builder {
        private String walls;
        private String roof;
        private boolean hasGarage;
        private boolean hasSwimmingPool;
        private boolean hasGarden;
        private boolean hasSolarPanels;

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

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

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

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

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

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

        public House build() {
            return new House(this);
        }
    }
}

Creating a House

House modernHouse = new House.Builder()
    .withWalls("concrete")
    .withRoof("metal")
    .withGarage(true)
    .withGarden(true)
    .build();

Reusing and Modifying Builders

One of the strengths of the Builder pattern is the ability to reuse a base configuration and modify only specific attributes.

House.Builder baseBuilder = new House.Builder()
    .withWalls("brick")
    .withRoof("shingle");

House baseHouse = baseBuilder.build();

House luxuryHouse = baseBuilder
    .withGarage(true)
    .withSwimmingPool(true)
    .withSolarPanels(true)
    .build();

This makes it easy to maintain consistent defaults while allowing customization.

Optional: Director Class

The Director class can encapsulate common configurations and construction sequences. It is often used in scenarios where the same sequence of steps is reused in multiple places.

public class HouseDirector {
    public House constructLuxuryHouse() {
        return new House.Builder()
            .withWalls("marble")
            .withRoof("tile")
            .withGarage(true)
            .withSwimmingPool(true)
            .withGarden(true)
            .withSolarPanels(true)
            .build();
    }

    public House constructSimpleHouse() {
        return new House.Builder()
            .withWalls("wood")
            .withRoof("shingle")
            .build();
    }
}

Advantages

  • Improved readability: Each step is labeled and explicit.
  • Better maintainability: Adding or removing optional parts does not require constructor overloads.
  • Encourages immutability: The product can be made immutable after construction.
  • Easier to test: Builders can be reused in unit tests to create customized test data.

When to Use

Consider using the Builder pattern when:

  • The constructor has many parameters, particularly optional ones
  • You want to avoid long lists of parameters that are hard to manage
  • You want more control and clarity in the object creation process
  • The object should be immutable once built

Conclusion

The Builder pattern is an effective way to construct objects that require a complex configuration. It separates the construction logic from the object itself, improving clarity and maintainability. This pattern is especially powerful in statically typed languages like Java, where constructor overloads and optional parameters can quickly become difficult to manage.