The Builder Pattern
The Builder pattern constructs a complex, usually immutable object step by step through a fluent helper class — replacing confusing multi-argument constructors with readable, named, chainable calls.
Learn The Builder Pattern in our free Java course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a quick reference.
Part of the free Java course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
You should know classes, constructors and access modifiers , and ideally the previous lesson on immutability — builders and immutable objects are natural partners.
💡 Analogy: A telescoping constructor is like ordering by shouting " one, twelve, true, false, true! " and hoping the kitchen guesses what each value means. A Builder is the sandwich shop counter: you say " start with a footlong, add cheese, add mushrooms, hold the pepperoni ", one clear instruction at a time, and only when you say " that's it " ( build() ) does the kitchen check your order is valid and hand over the finished, sealed sandwich. You configured a mutable order; you received an immutable meal.
Each instruction is named , so nobody confuses cheese for mushrooms — and the order can't go out the door until it passes the final check.
With several optional fields you end up with a ladder of constructors, and call sites become a row of mystery true / false values. Which flag is which? You can't tell without opening the class. Add one option and you add another constructor.
Move construction into a static nested Builder . Required values go to the builder's constructor; optional ones get named methods that set a field and return this so calls chain. The product's constructor is private and takes the builder, so objects can only be made the validated way.
The setters just record values; build() is the one place where the whole object exists, so it's where you validate. Check required fields and cross-field rules there, throwing IllegalStateException if anything is off — an invalid object is then never created.
Answer: chaining would break — builder.cheese().mushroom() wouldn't compile because cheese() would return nothing for mushroom() to call. Each fluent method must return this .
Answer: so the only path to an instance is through the builder's build() , which guarantees validation runs and the object is complete. A public constructor would let callers bypass all of that.
Answer: in build() . Only there does the whole object exist, so cross-field rules can be checked. Setters just record intent.
🎯 YOUR TURN — Finish the Burger builder
Complete the nested Builder : a patties(int) setter, a no-arg cheese() flag, and build() — all chainable.
🧩 MINI-CHALLENGE — Config builder with defaults & validation
Build an immutable HttpRequestConfig with a required URL, defaulted method/timeout, and validation in build() .
You can now recognise the telescoping-constructor problem, write a static nested Builder with fluent return this setters, validate in build() , produce an immutable result through a private constructor, and judge when a builder beats a plain constructor or record.
Next up: Dependency Injection — wiring objects together from the outside so your code is decoupled and testable.
Practice quiz
What problem does the Builder pattern solve?
- Slow loops
- Memory leaks
- Telescoping constructors with many parameters
- Thread starvation
Answer: Telescoping constructors with many parameters. It replaces a pile of confusing multi-argument constructors with readable, named, step-by-step construction.
Where is the Builder usually defined?
- As a static nested class inside the product
- In a separate package
- As a global function
- In the main method
Answer: As a static nested class inside the product. The Builder is conventionally a static nested class of the class it builds, with access to its private constructor.
Why do fluent setter methods return 'this'?
- To save memory
- To make them static
- Required by the compiler
- To allow method chaining
Answer: To allow method chaining. Returning the builder itself lets you chain calls: builder.cheese().mushroom().build().
What does the build() method typically do?
- Nothing
- Validate, then create the immutable product
- Print the object
- Reset the builder
Answer: Validate, then create the immutable product. build() is the single place to validate the accumulated state and then construct the final, usually immutable, object.
Why make the product's constructor private?
- So objects can only be created through the Builder
- For speed
- To allow subclassing
- It's optional styling
Answer: So objects can only be created through the Builder. A private constructor forces all construction through the builder, guaranteeing validation runs and the object is fully formed.
How are required vs optional fields usually handled?
- All in setters
- All optional
- Required via the builder constructor, optional via fluent setters
- Required via build()
Answer: Required via the builder constructor, optional via fluent setters. Required values are passed to the Builder's constructor; optional ones get fluent setters with sensible defaults.
The object produced by a good builder is typically...
- mutable
- immutable
- static
- abstract
Answer: immutable. Builders pair naturally with immutability — the builder mutates, the finished product does not.
When is the Builder pattern most worth it?
- For a 1-field class
- For utility methods
- For enums
- For classes with many optional parameters
Answer: For classes with many optional parameters. It shines when there are several optional parameters; for one or two fields a plain constructor is simpler.
What does method chaining look like?
- b; cheese; build;
- b.cheese().mushroom().build()
- build(b, cheese, mushroom)
- new B(cheese, mushroom)
Answer: b.cheese().mushroom().build(). Each fluent method returns the builder so the next call attaches directly, ending in build().
Which JDK class uses a builder-like fluent API?
- ArrayList
- Integer
- HttpRequest.newBuilder()
- Math
Answer: HttpRequest.newBuilder(). HttpRequest.newBuilder()...build() and Stream.Builder are real fluent-builder examples in the JDK.