Back to Creational: Singleton, Factory, Builder, Prototype
Q4Question 4 of 12

Explain the Builder design pattern and how method chaining works

Key Concepts
  • Builder constructs a complex object step by step through a dedicated builder object, then locks it with build().
  • Solves the telescoping-constructor problem: many optional fields with positional arguments are unreadable and error-prone.
  • Method chaining works because each setter returns the builder itself (return this), enabling a fluent, readable sequence.
  • build() validates invariants and produces an immutable object; invalid objects can never exist.
  • Required fields belong in the builder's constructor; optional fields get fluent setters with defaults.
  • In modern Java, Lombok @Builder generates the pattern; hand-written form is what interviewers expect.

Definition

The Builder assembles a complex object field by field through chained calls and locks it with build(), replacing an unreadable constructor with many parameters.
  • Builder pattern – a creational pattern that constructs a complex object step by step through a dedicated builder object.
  • Telescoping-constructor problem – a class with many fields ends up with confusing overloaded constructors; Builder replaces them.
  • Method chaining – each builder setter returns this, so calls chain into a fluent, readable sequence ending in build().
  • Immutability – the target object can have all final fields and a private constructor that only the builder calls.
  • Validation – build() is the single choke point to enforce invariants; invalid objects can never exist.

When Rajesh applies for a home loan at the National Bank of India, he does not blurt out all fifteen details in one breath. He fills the long application form one field at a time: name, amount, tenure, property value, co-applicant. The half-filled form is the builder, each pen stroke is a chained setter, and pressing submit is the build() call that produces the final, locked loan application.

Understanding the Concepts

What is the telescoping-constructor problem?

When a class has many fields, especially optional ones, developers end up writing many overloaded constructors. A call like new Trade("T1", "RELIANCE", 100, 2450, 50, null, true) is unreadable, and it is easy to swap two numbers by accident.

JAVA
package com.bank.model;

import java.math.BigDecimal;

// PROBLEM: what does each argument mean?
public class TradeBad {
    public TradeBad(String id, String symbol, int qty, BigDecimal price,
                    BigDecimal fee, String notes, boolean urgent) {
        // 7 positional parameters – error-prone
    }
}
// new TradeBad("T1", "RELIANCE", 100, price, fee, null, true)
// Was 100 the quantity or something else?

How does the Builder pattern fix this?

The Builder gives each field a named method, so the intent is obvious, and builds the real object at the end.

JAVA
package com.bank.model;

import java.math.BigDecimal;

public final class TradeOrder {
    private final String tradeId;
    private final String symbol;
    private final int quantity;
    private final BigDecimal price;
    private final BigDecimal fee;
    private final String notes;
    private final boolean urgent;

    private TradeOrder(Builder b) {
        this.tradeId = b.tradeId;
        this.symbol = b.symbol;
        this.quantity = b.quantity;
        this.price = b.price;
        this.fee = b.fee;
        this.notes = b.notes;
        this.urgent = b.urgent;
    }

    public static Builder builder() { return new Builder(); }

    public static class Builder {
        private String tradeId;
        private String symbol;
        private int quantity;
        private BigDecimal price;
        private BigDecimal fee = BigDecimal.ZERO;
        private String notes = "";
        private boolean urgent = false;

        public Builder tradeId(String v) { this.tradeId = v; return this; }
        public Builder symbol(String v) { this.symbol = v; return this; }
        public Builder quantity(int v) { this.quantity = v; return this; }
        public Builder price(BigDecimal v) { this.price = v; return this; }
        public Builder fee(BigDecimal v) { this.fee = v; return this; }
        public Builder notes(String v) { this.notes = v; return this; }
        public Builder urgent(boolean v) { this.urgent = v; return this; }

        public TradeOrder build() {
            if (tradeId == null || symbol == null) {
                throw new IllegalStateException("tradeId and symbol are required");
            }
            if (quantity <= 0) {
                throw new IllegalStateException("quantity must be positive");
            }
            if (price == null || price.signum() <= 0) {
                throw new IllegalStateException("price must be positive");
            }
            return new TradeOrder(this);
        }
    }

    @Override
    public String toString() {
        return tradeId + ": " + quantity + " " + symbol + " @ " + price
            + " fee=" + fee + (urgent ? " [URGENT]" : "");
    }
}

How does method chaining work?

Each setter returns the builder itself with return this. Since each call hands back the same builder, you can immediately call the next setter, forming a chain.

JAVA
package com.bank.demo;

import java.math.BigDecimal;

public class BuilderDemo {
    public static void main(String[] args) {
        TradeOrder order = TradeOrder.builder()
            .tradeId("TRD-001")
            .symbol("RELIANCE")
            .quantity(100)
            .price(new BigDecimal("2450.00"))
            .fee(new BigDecimal("50.00"))
            .urgent(true)
            .build();

        System.out.println(order);
    }
}
TEXT
TRD-001: 100 RELIANCE @ 2450.00 fee=50.00 [URGENT]

The chain reads almost like a sentence, which is why it is called a fluent API.

How does Builder enable immutability?

The target class makes all fields final and has a private constructor that only the builder calls. The builder gathers mutable state, and build() freezes it into an immutable object.

JAVA
// All fields are final – set once in the constructor
private final String tradeId;
private final String symbol;
private final int quantity;
private final BigDecimal price;

// Private constructor – only Builder can call it
private TradeOrder(Builder b) { ... }

An immutable TradeOrder is safe to share across settlement, audit, and notification threads without locks.

How does build() enforce validation?

The build() method is the single choke point where invariants are enforced. An invalid object can never exist.

JAVA
package com.bank.demo;

public class BuilderValidationDemo {
    public static void main(String[] args) {
        try {
            // Missing required fields – build() throws
            TradeOrder bad = TradeOrder.builder()
                .tradeId("TRD-002")
                .quantity(-5) // invalid
                .build();
        } catch (IllegalStateException e) {
            System.out.println("Rejected: " + e.getMessage());
        }
    }
}
TEXT
Rejected: symbol is required

How do you separate required from optional fields?

Required fields belong in the builder's constructor so they cannot be forgotten:

JAVA
public static class StrictBuilder {
    private final String tradeId;  // required
    private final String symbol;   // required
    private int quantity;          // optional with default
    private BigDecimal fee = BigDecimal.ZERO;

    public StrictBuilder(String tradeId, String symbol) {
        this.tradeId = tradeId;
        this.symbol = symbol;
    }

    public StrictBuilder quantity(int v) { this.quantity = v; return this; }
    public StrictBuilder fee(BigDecimal v) { this.fee = v; return this; }
    // build()...
}

// Usage: required fields cannot be forgotten
// new StrictBuilder("TRD-003", "HDFC").quantity(50).build();

Where does Builder appear in the real world?

  • StringBuilder chains append() calls to build a String.
  • Stream.Builder assembles stream elements.
  • Lombok @Builder generates the builder pattern automatically.
  • Spring UriComponentsBuilder builds URIs fluently.

What are common mistakes with Builder?

  • Forgetting validation in build(), allowing invalid objects to exist.
  • Using Builder for a class with only two or three fields – a plain constructor is simpler.
  • Making the built object mutable, defeating the purpose of the pattern.

What should a senior engineer know?

  • Builder shines when there are many optional fields or when immutability with validation is needed.
  • For a class with two or three required fields, a plain constructor is simpler.
  • Lombok @Builder generates the pattern, but interviewers want to see the hand-written form.
  • In a banking system, a validated, immutable trade order means the object is correct from birth and cannot be tampered with.

Visual Overview

DIAGRAM
  Builder Pattern Flow
  ─────────────────────

  TradeOrder.builder()
       │
       ▼
  ┌──────────────────┐
  │     Builder       │  (mutable, accumulates fields)
  │ .tradeId("T1")   │──▶ returns this
  │ .symbol("REL")   │──▶ returns this
  │ .quantity(100)    │──▶ returns this
  │ .price(2450)      │──▶ returns this
  │ .fee(50)          │──▶ returns this
  │ .build()          │
  └────────┬─────────┘
           │ validates invariants
           ▼
  ┌──────────────────┐
  │   TradeOrder      │  (immutable, all fields final)
  │   tradeId = T1    │
  │   symbol = REL    │
  │   quantity = 100  │
  │   price = 2450    │
  │   fee = 50        │
  └──────────────────┘

Key Takeaway

The Builder pattern constructs a complex object step by step through a separate builder object and produces the final object with a build() call. The problem it solves is the telescoping-constructor problem, where a class with many fields ends up with confusing overloaded constructors. A call like new Order with eight positional arguments is unreadable, and in a bank swapping the price and fee arguments debits the wrong amount.

The Builder lets Rajesh set each field through a named method, so the intent is obvious. Method chaining makes it fluent: each setter returns this, so the calls chain and the whole thing reads like a sentence. The build() method validates invariants and returns the immutable result.

Two benefits matter especially in banking. Immutability: the target has all final fields, so once a trade order is built, nothing can change its quantity or price, making it safe across settlement and audit threads. Validation: build() is the single choke point, so an order with a missing symbol or negative quantity can never come into existence.

At National Bank of India, Pankaj builds trade orders this way, and the senior judgment is that Builder earns its place when there are many optional fields or when immutability with validation is needed. For a class with two or three required fields, a plain constructor is simpler.

Remember one sentence: Builder replaces unreadable multi-parameter constructors with named, chained setters and a validating build() that produces an immutable object.

---

Share
Use ← → arrow keys to navigate