Builder and Singleton Patterns
How to assemble complex immutable objects readably, and the one singleton form that is hard to get wrong.
Open this lesson in the learning hubKey points
- Builder: for objects with many optional fields. Named steps beat a constructor with seven arguments you must count.
- Each step returns
thisso calls chain, andbuild()hands back one finished immutable object. - Put required values in the builder constructor and validate inside
build(), so half-built objects never escape. - Singleton: exactly one instance. An
enumwith a single constant is the simplest correct version in Java. - That enum form is thread safe, lazy at class-load time, and immune to the reflection and serialization tricks.
- Singletons are global state and make tests painful. Prefer one instance managed by your framework and injected.
Example
public class Main {
// BUILDER: many optional settings, result is still immutable and readable.
static final class HttpConfig {
private final String url;
private final int timeoutMs;
private final boolean retry;
private HttpConfig(Builder b) {
this.url = b.url;
this.timeoutMs = b.timeoutMs;
this.retry = b.retry;
}
static Builder builder(String url) { return new Builder(url); }
@Override public String toString() {
return "HttpConfig[url=" + url + ", timeoutMs=" + timeoutMs + ", retry=" + retry + "]";
}
static final class Builder {
private final String url; // required
private int timeoutMs = 2000; // sensible defaults
private boolean retry = false;
private Builder(String url) { this.url = url; }
Builder timeoutMs(int ms) { this.timeoutMs = ms; return this; }
Builder retry(boolean on) { this.retry = on; return this; }
HttpConfig build() { return new HttpConfig(this); }
}
}
// SINGLETON: an enum is the simplest thread-safe, serialization-safe version.
enum Counter {
INSTANCE;
private int hits;
int hit() { return ++hits; }
}
public static void main(String[] args) {
System.out.println(HttpConfig.builder("https://api.dev").build());
System.out.println(HttpConfig.builder("https://api.dev")
.timeoutMs(500)
.retry(true)
.build());
Counter.INSTANCE.hit();
Counter.INSTANCE.hit();
System.out.println("hits = " + Counter.INSTANCE.hit());
System.out.println("one instance? " + (Counter.INSTANCE == Counter.valueOf("INSTANCE")));
}
}
Build immutable objects step by step, and keep singletons injected rather than global.
This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the OOP course, and every lesson in it is listed on the OOP contents page.