Implement Builder in Java when construction has enough optionality or validation pressure to justify a clearer staged creation model.
Builder: A construction pattern that separates how an object is assembled from the final object representation, especially when configuration is optional or staged.
Builder is one of the most practical Java patterns because it solves a real readability problem: direct constructors become hard to understand once many arguments, optional settings, or validation rules accumulate.
1public final class ReportConfig {
2 private final String title;
3 private final int timeoutSeconds;
4 private final boolean includeCharts;
5
6 private ReportConfig(Builder builder) {
7 this.title = builder.title;
8 this.timeoutSeconds = builder.timeoutSeconds;
9 this.includeCharts = builder.includeCharts;
10 }
11
12 public static final class Builder {
13 private String title;
14 private int timeoutSeconds = 30;
15 private boolean includeCharts;
16
17 public Builder title(String title) {
18 this.title = title;
19 return this;
20 }
21
22 public Builder timeoutSeconds(int timeoutSeconds) {
23 this.timeoutSeconds = timeoutSeconds;
24 return this;
25 }
26
27 public Builder includeCharts(boolean includeCharts) {
28 this.includeCharts = includeCharts;
29 return this;
30 }
31
32 public ReportConfig build() {
33 if (title == null || title.isBlank()) {
34 throw new IllegalStateException("title is required");
35 }
36 return new ReportConfig(this);
37 }
38 }
39}
This is useful because the construction process becomes readable and validation has a clear home.
Builder is a good fit when:
The construction flow is often easier to review than a long positional constructor.
The diagram below shows the shape:
flowchart LR
Start["Builder starts with defaults"] --> Setters["Caller sets optional and required fields"]
Setters --> Validate["Build step validates invariants"]
Validate --> Product["Immutable object is created"]
Builder becomes unnecessary when:
A tiny data carrier should not automatically grow a nested builder just because the pattern exists.
One of the strongest reasons to use Builder is that build() creates a visible validation boundary. That is where you can:
If the builder is present but still lets invalid objects leak through, much of its value is gone.
When reviewing a Java builder, ask:
build()?Builder earns its weight when it removes ambiguity and makes invariants visible.