Definition
- 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 inbuild(). - Immutability – the target object can have all
finalfields 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.
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.
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.
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);
}
}
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.
// 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.
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());
}
}
}
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:
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?
StringBuilderchainsappend()calls to build aString.Stream.Builderassembles stream elements.- Lombok
@Buildergenerates the builder pattern automatically. - Spring
UriComponentsBuilderbuilds 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
@Buildergenerates 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
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.
---