SSourav Saha
HomeExperienceSoftware DesignSystem DesignLearningBooksToolsContact
SSourav Saha

Building scalable backend systems, distributed infrastructure, and cloud-native applications.

Navigation

  • Home
  • Experience
  • Software Design
  • System Design
  • Learning

More

  • Books
  • Tools
  • Contact

Connect

  • LinkedIn
  • Email

© 2026 Sourav Saha. All rights reserved.

Built with using Next.js

Software DesignBuilder Pattern
Software DesignCreational Patterns

Builder Pattern

Construct complex objects step by step, separating construction from representation to avoid telescoping constructors.

March 11, 20255 min read
design-patternsjavabuildercreational

One-line Definition

Encapsulates object construction step by step, allowing the same construction process to create different representations.


Problem

Suppose you have a class with many optional fields. You might end up with a dozen constructor overloads (the "telescoping constructor" anti-pattern) or a single constructor with 10 parameters where clients pass null for things they don't need. This makes instantiation error-prone and hard to read. The Builder Pattern solves this by moving object construction into a dedicated builder object.


Real-world Analogy

Think of ordering a custom burger. You don't hand the cashier a list of 12 ingredients in exact order — you say "add lettuce," "add cheese," "no onions," "extra sauce." Each step is named and optional. The builder assembles your burger one topping at a time, and only at the end do you get the finished product.


Structure

  • Client
    • Associates a Builder with a Director (optional) or directly interacts with the Builder to assemble the object.
  • Product
    • The complex object being built.
  • Builder
    • Interface or nested static class with setter-like methods that return this for chaining.
  • Director (optional)
    • Orchestrates the build steps in a predefined order.

Diagram

classDiagram
    class Client {
    }
    class Order {
        -String orderId
        -String customerId
        -String status
    }
    class Builder {
        +orderId(id) Builder
        +customerId(id) Builder
        +status(status) Builder
        +build() Order
    }
    
    Client --> Builder : configures
    Builder ..> Order : creates
    Order +-- Builder : nested inside

Code Walkthrough

Notice that Order is instantiated by its nested Builder. The client names every parameter it passes in, making the code self-documenting.

public class Order {
    private final String orderId;
    private final String customerId;
    private final String date;
    private final String status;
    private final double subtotal;
    private final double tax;
    private final String shippingCity;
    private final boolean isPriority;

    private Order(Builder builder) {
        this.orderId = builder.orderId;
        this.customerId = builder.customerId;
        this.date = builder.date;
        this.status = builder.status;
        this.subtotal = builder.subtotal;
        this.tax = builder.tax;
        this.shippingCity = builder.shippingCity;
        this.isPriority = builder.isPriority;
    }

    public static class Builder {
        private final String orderId;     // Required
        private final String customerId;  // Required
        private String date;
        private String status = "PENDING";
        private double subtotal;
        private double tax;
        private String shippingCity;
        private boolean isPriority = false;

        public Builder(String orderId, String customerId) {
            this.orderId = orderId;
            this.customerId = customerId;
        }

        public Builder date(String date) { this.date = date; return this; }
        public Builder subtotal(double subtotal) { this.subtotal = subtotal; return this; }
        public Builder tax(double tax) { this.tax = tax; return this; }
        public Builder shippingCity(String city) { this.shippingCity = city; return this; }
        public Builder priority(boolean priority) { this.isPriority = priority; return this; }

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

// Usage
Order order = new Order.Builder("ORD-123", "CUST-456")
    .date("2024-01-15")
    .subtotal(1500.0)
    .tax(270.0)
    .shippingCity("Mumbai")
    .priority(true)
    .build();

Bad vs Good

Bad Approach

Problems

  • Impossible to tell what each positional argument means.
  • Adding a new optional field requires a new constructor overload.
  • Easy to swap two arguments of the same type (like city and pincode).
public class Order {
    public Order(String orderId, String customerId, String date,
                 String status, String currency, double subtotal,
                 double tax, double total, String city,
                 String pincode, boolean isPriority, String shippingMode) {
        // assign all fields...
    }
}

// Caller: which argument is the city? which is the pincode?
Order order = new Order("ORD-123", "CUST-456", "2024-01-15",
    "PENDING", "INR", 1500.0, 270.0, 1770.0, "Mumbai",
    "400001", true, "Express");

Better Approach

Improvements

  • Every parameter is named — no positional ambiguity.
  • New fields are added by extending the builder, keeping object creation flexible.
Order order = new Order.Builder("ORD-123", "CUST-456")
    .date("2024-01-15")
    .subtotal(1500.0)
    .tax(270.0)
    .shippingCity("Mumbai")
    .priority(true)
    .build();

Pros vs Cons

ProsCons
Self-documenting object creationMore boilerplate code to write and maintain
Handles optional parameters gracefullyOverkill for simple objects with few fields
Makes creating immutable objects easyDuplicated field declarations between Builder and Product
Allows validation before instantiationRequires creating an extra object in memory (the builder)
Avoids the telescoping constructor anti-pattern

When to Use

  • An object has more than 4 parameters, especially optional ones.
  • You want to construct immutable objects safely.
  • The construction process involves multiple complex steps.
  • You want to avoid confusing constructor overloads.

When Not to Use

  • The object has 2–3 required fields and no optional fields.
  • The object is highly mutable and setters work just fine.
  • The added boilerplate code outweighs the readability benefits.

Real-world Examples

  • java.lang.StringBuilder (and StringBuffer)
  • java.util.stream.Stream.Builder
  • java.net.http.HttpRequest.newBuilder()
  • Lombok's @Builder annotation

Key Takeaway

The Builder Pattern eliminates unreadable constructors by letting you name every parameter and set only what you need. Use it when constructing complex, parameter-heavy objects, but stick to a simple constructor if the object only has a couple of fields.

PreviousProxy PatternNextFlyweight Pattern