Static factories & the Builder pattern
Named constructors, the Builder for optional fields, and when each beats a telescoping constructor.
The problem: too many constructor parameters
When a class has many optional fields, you end up with a "telescoping constructor" — one overload per combination of optional parameters:
new Connection("host", 5432, "user", "pass", 30, true);
// Which argument is the timeout? Which is the port?
Two patterns solve this at different scales.
Static factory methods
A static factory is a static method that constructs and returns an instance. The constructor is kept private:
public final class Temperature {
private final double celsius;
private Temperature(double c) { this.celsius = c; }
public static Temperature ofCelsius(double c) { return new Temperature(c); }
public static Temperature ofFahrenheit(double f) { return new Temperature((f - 32) * 5 / 9); }
public static Temperature ofKelvin(double k) { return new Temperature(k - 273.15); }
public double toCelsius() { return celsius; }
}
Advantages over constructors: descriptive names (ofCelsius vs Temperature), can return a cached or subtype instance, and can enforce invariants before construction. Standard library examples: List.of(), Optional.of(), LocalDate.of(), Integer.valueOf().
The Builder pattern
A fluent Builder is the right tool when you have many optional fields and readability matters:
public static final class Builder {
private final String to;
private final String subject;
private String body = "";
private String cc;
public Builder(String to, String subject) {
this.to = to; this.subject = subject;
}
public Builder body(String body) { this.body = body; return this; }
public Builder cc(String cc) { this.cc = cc; return this; }
public EmailMessage build() { return new EmailMessage(this); }
}
// Usage:
EmailMessage msg = new EmailMessage.Builder("alice@example.com", "Hello")
.body("Hi Alice!")
.build();
Each setter returns this so calls can be chained. Required fields go in the Builder constructor; optional ones have setters with defaults. build() is the single place to validate invariants.
When to use which
| Situation | Reach for | |---|---| | Multiple unit-aware constructors | Static factory | | Caching / singleton | Static factory | | 4+ optional fields | Builder | | Simple required-only fields | Plain constructor | | Immutable value object, all required | record |