AlgoMaster Logo

Builder Design Pattern

Ashish

Ashish Pratap Singh

5 min read

The Builder Design Pattern is a creational pattern that lets you construct complex objects step-by-step, separating the construction logic from the final representation.

It’s particularly useful in situations where:

  • An object requires many optional fields, and not all of them are needed every time.
  • You want to avoid telescoping constructors or large constructors with multiple parameters.
  • The object construction process involves multiple steps that need to happen in a particular order.

When building such objects, developers often rely on constructors with many parameters or expose setters for every field. For example, a User class might have fields like nameemailphoneaddress, and preferences.

But as the number of fields grows, this approach becomes hard to manage, error-prone, and violates the Single Responsibility Principle — mixing construction logic with business logic.

The Builder Pattern solves this by introducing a separate builder class that handles the object creation process. The client uses this builder to construct the object step-by-step, while keeping the final object immutable, consistent, and easy to create.

Let’s walk through a real-world example to see how we can apply the Builder Pattern to make complex object creation cleaner, safer, and more maintainable.

1. The Problem: Building Complex HttpRequest Objects

Imagine you're building a system that needs to configure and create HTTP requests. Each HttpRequest can contain a mix of required and optional fields depending on the use case.

Here’s what a typical HTTP request might include:

  • URL (required)
  • HTTP Method (e.g., GET, POST, PUT – defaults to GET)
  • Headers (optional, multiple key-value pairs)
  • Query Parameters (optional, multiple key-value pairs)
  • Request Body (optional, typically for POST/PUT)
  • Timeout (optional, default to 30 seconds)

At first glance, it seems manageable. But as the number of optional fields increases, so does the complexity of object construction.

The Naive Approach: Telescoping Constructors

A common approach is to use constructor overloading often referred to as the telescoping constructor anti-pattern. Here you define multiple constructors with increasing numbers of parameters:

Example Client Code

What’s Wrong with This Approach?

While it works functionally, this design quickly becomes unwieldy and error-prone as the object becomes more complex.

1. Hard to Read and Write

  • Multiple parameters of the same type (e.g., StringMap) make it easy to accidentally swap arguments.
  • Code is difficult to understand at a glance especially when most parameters are null.

2. Error-Prone

  • Clients must pass null for optional parameters they don’t want to set, increasing the risk of bugs.
  • Defensive programming inside constructors becomes necessary to avoid NullPointerExceptions.

3. Inflexible and Fragile

  • If you want to set parameter 5 but not 3 and 4, you’re forced to pass null for 3 and 4.
  • You must follow the exact parameter order, which hurts readability and usability.

4. Poor Scalability

  • Adding a new optional parameter requires adding or changing constructors, which may break existing code or force unnecessary updates to the client.
  • Testing and documentation become increasingly difficult to maintain.

What We Need Instead

We need a more flexible, readable, and maintainable way to construct HttpRequest objects — especially when many optional values are involved and different combinations are needed.

This is exactly where the Builder Design Pattern comes in.

2. What is the Builder Pattern

The Builder pattern separates the construction of a complex object from its representation.

In the Builder Pattern:

  • The construction logic is encapsulated in a Builder.
  • The final object (the "Product") is created by calling a build() method.
  • The object itself typically has a private or package-private constructor, forcing construction through the builder.

This leads to readable, fluent code that’s easy to extend and modify without breaking existing clients.

Class Diagram

Builder (e.g., HttpRequestBuilder)

  • Defines methods to configure or set up the product.
  • Typically returns this from each method to support a fluent interface.
  • Often implemented as a static nested class inside the product class.

ConcreteBuilder (e.g., StandardHttpRequestBuilder)

  • Implements the Builder interface or defines the fluent methods directly.
  • Maintains state for each part of the product being built.
  • Implements the build() method that returns the final product instance.

Product (e.g., HttpRequest)

  • The final object being constructed.
  • May be immutable and built only via the Builder.
  • Has a private constructor that takes in the builder’s internal state.

Director (Optional) (e.g., HttpRequestDirector)

  • Orchestrates the building process using the builder.
  • Useful when you want to encapsulate standard configurations or reusable construction sequences.
  • In modern usage (especially in Java with fluent builders), the Director is often omitted, and the client takes on this role by chaining methods.

3. Implementing Builder

1. Create the Product Class (HttpRequest)

We start by creating the HttpRequest class — the product we want to build. It has multiple fields (some required, some optional), and its constructor will be private, forcing clients to construct it via the builder.

The builder class will be defined as a static nested class within HttpRequest, and the constructor will accept an instance of that builder to initialize the fields.

This builder class allows clients to set up each part of the request through a fluent interface.

2. Using the Builder from Client Code

Let’s see how easy and readable it is to construct an HttpRequest using the builder:

What We Achieved

  • No need for long constructors or null arguments.
  • Optional values are clearly named and easy to set.
  • The final object is immutable and fully initialized.
  • Readable and fluent client code.
  • Easily extendable. Want to add a new optional field? Just add a new method to the builder.