Memra

Static factories & the Builder pattern

◈ 4 cards

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

SituationReach for
Multiple unit-aware constructorsStatic factory
Caching / singletonStatic factory
4+ optional fieldsBuilder
Simple required-only fieldsPlain constructor
Immutable value object, all requiredrecord
BuilderBuilderproductnew Builder(to, subject)required fields.body("Hi Alice!")returns thisbuild()validate hereEmailMessageimmutable
Every setter returns this, which is what keeps the chain going. build() is the one gate where the accumulated state becomes a finished object.
NORMAL ~/memra/learn/java-from-zero/static-factories-and-builder utf-8 LF