Chaturmind
LearnDSASystem DesignBlogPremium
Sign inGet started
Chaturmind

Structured learning paths for engineers who want to go deep. Written by practitioners.

Learn

  • Java
  • DSA
  • System Design
  • Spring Boot
  • AI / ML

Company

  • Blog
  • Premium
  • Contact

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Chaturmind. All rights reserved.

Built for engineers who want to go deep.


← Low-Level Design & Design Patterns

OOP Fundamentals & SOLID Principles

  • Object-Oriented Design Refresher
  • Single Responsibility & Open/Closed
  • Liskov Substitution & Interface Segregation
  • Dependency Inversion

Creational Design Patterns

  • Singleton Pattern
  • Factory & Abstract Factory
  • Builder Pattern
  • Prototype Pattern

Structural Design Patterns

  • Adapter & Facade
  • Decorator Pattern
  • Composite & Proxy
  • Bridge & Flyweight
Chaturmind
← Low-Level Design & Design Patterns

OOP Fundamentals & SOLID Principles

  • Object-Oriented Design Refresher
  • Single Responsibility & Open/Closed
  • Liskov Substitution & Interface Segregation
  • Dependency Inversion

Creational Design Patterns

  • Singleton Pattern
  • Factory & Abstract Factory
  • Builder Pattern
  • Prototype Pattern

Structural Design Patterns

  • Adapter & Facade
  • Decorator Pattern
  • Composite & Proxy
  • Bridge & Flyweight
HomeLearnSystem DesignLow-Level Design & Design PatternsCreational Design Patterns
✓ FreeBeginner· 7 min read

Builder Pattern

Escaping telescoping constructors with a fluent, method-chaining builder API — built through an HttpRequest example with required and optional fields.

Published September 22, 2026


Builder Pattern

The problem: telescoping constructors

class HttpRequest {
    HttpRequest(String url) { ... }
    HttpRequest(String url, String method) { ... }
    HttpRequest(String url, String method, Map<String,String> headers) { ... }
    HttpRequest(String url, String method, Map<String,String> headers, String body) { ... }
    HttpRequest(String url, String method, Map<String,String> headers, String body, int timeoutMs) { ... }
}

Each new optional field means another constructor overload, and callers with several optional parameters set have to pass null/defaults through ones they don't care about, in a fixed, easy-to-mix-up positional order — new HttpRequest(url, null, null, body, 5000) gives no clue at the call site which value is which.

The Builder fix: step-by-step construction

class HttpRequest {
    private final String url;          // required
    private final String method;        // required
    private final Map<String,String> headers;
    private final String body;
    private final int timeoutMs;

    private HttpRequest(Builder b) {
        this.url = b.url;
        this.method = b.method;
        this.headers = b.headers;
        this.body = b.body;
        this.timeoutMs = b.timeoutMs;
    }

    static class Builder {
        private final String url;       // required — passed to Builder's constructor
        private final String method;    // required
        private Map<String,String> headers = new HashMap<>();
        private String body = null;
        private int timeoutMs = 30_000; // sensible default

        Builder(String url, String method) { // required fields enforced here
            this.url = url;
            this.method = method;
        }

        Builder header(String key, String value) { headers.put(key, value); return this; }
        Builder body(String body) { this.body = body; return this; }
        Builder timeoutMs(int ms) { this.timeoutMs = ms; return this; }

        HttpRequest build() { return new HttpRequest(this); }
    }
}

The fluent API — why every method returns this

HttpRequest req = new HttpRequest.Builder("https://api.example.com/users", "POST")
    .header("Content-Type", "application/json")
    .body("{\"name\":\"Alice\"}")
    .timeoutMs(5000)
    .build();

Each setter-style method returns this (the builder instance), which is exactly what allows the calls to chain into one fluent expression instead of a sequence of separate statements. This reads almost like a sentence describing the object being built — every optional field is named explicitly at the call site, unlike a long positional constructor call.

Required vs optional fields — where each belongs

The design decision that makes this actually safe, not just readable: url and method are required, so they're passed into the Builder's own constructor — there's no way to call build() without having supplied them, since there's no way to construct the Builder itself without them. Everything genuinely optional (headers, body, timeoutMs) gets its own chainable setter with a sensible default. This split is what prevents a builder from just becoming "a telescoping constructor with extra steps" — required-ness is enforced by the type system's constructor requirement, not by convention or a runtime null-check in build().

Follow-up questions this topic invites — and their answers

Q: Why make the outer class's constructor private and only reachable through the Builder? A: It forces every caller through the builder's required-field constructor and its build() step — there's no back door that lets a caller construct an HttpRequest directly and skip validation or default-filling that build() might otherwise perform.

Q: How is this different from just using a mutable setter-based POJO (new HttpRequest(); req.setUrl(...); req.setMethod(...);)? A: Two differences that matter: the final object can be genuinely immutable (the Builder holds the mutable state during construction; the built object's fields are final), and required fields are enforced at compile time via the Builder's constructor — a plain setter-based POJO gives no compile-time guarantee that setUrl() was ever called before the object gets used.

Q: When would a Builder be overkill? A: For an object with two or three fields, all effectively required — a normal constructor is clearer and the Builder's extra ceremony (a nested class, chained setters, a build() call) doesn't earn its cost. Builder pays off specifically when there's a meaningful mix of required and optional fields, typically four or more total.

Q: Is Lombok's @Builder annotation the same pattern? A: Functionally yes — it generates this exact structure (a static nested Builder class with chained setters and a build() method) at compile time, which is why Lombok's @Builder is so commonly reached for once a team recognizes they're about to hand-write this pattern for the third time.

Previous

Factory & Abstract Factory

Next

Prototype Pattern

AI Tutor

Lesson: Builder Pattern

Quick actions

AI responses can be inaccurate. Verify critical information.