Skip to content

Add opt-in staged / step builder for compile-time required-field enforcement #224

Description

@AndreasIgel

Status: future idea. This is a large, opt-in feature with a substantial increase in generated code (a chain of step interfaces per required field). It is captured for future consideration, not scheduled work.

Feature (future idea): opt-in staged / step ("telescoping") builder

Summary

Add an opt-in generation mode that produces a staged builder: instead of one builder type exposing all setters, the processor generates a chain of small step interfaces so the compiler forces required fields to be set, in order, before build() is reachable. Required-field violations become compile errors instead of the runtime IllegalStateException SB already throws.

Example

Person p = PersonBuilder.builder()
    .name("Jane")     // NameStep  -> returns AgeStep
    .age(30)          // AgeStep   -> returns BuildStep
    .build();         // BuildStep -> build() only appears here

// PersonBuilder.builder().build();          // ❌ won't compile — name/age not set
// PersonBuilder.builder().age(30).name(..); // ❌ won't compile — wrong order

Generated shape (illustrative):

interface NameStep  { AgeStep name(String n); }
interface AgeStep   { BuildStep age(int a); }
interface BuildStep { Person build(); /* optional-field setters live here */ }

Relationship to existing behavior

SB already enforces required/non-null fields at runtime in build() (a field is required when it is a primitive, a constructor parameter, or carries a @NotNull/@NonNull-named annotation). Staged builders would be the compile-time complement to that runtime enforcement — the same "required" set, but caught by the type system instead of at build time.

Why this is a future idea, not scheduled work

  • Massive codegen. Every required field adds a dedicated step interface plus the wiring between steps; the generated surface grows a lot compared to the current single-builder output. This is a significant increase in complexity and maintenance in the generator.
  • Polarizing ergonomics. Forced ordering and many step types are loved by some teams and disliked by others; it makes the API more rigid.
  • Marginal benefit over what exists. Runtime enforcement already prevents building an invalid object. Staged builders only move the failure from build time to compile time.
  • "Required but nullable" barely exists. The niche this would most help — a field you must set but may set to null — is essentially never needed in practice, so the extra machinery buys little.

If ever implemented — design constraints

  • Strictly opt-in via a new option (e.g. staged) in CompilerArgumentsEnum / SimpleBuilder.Options, off by default; the current single-builder output must remain the default and unchanged.
  • Reuse the existing required-field detection (primitive / constructor param / @NotNull/@NonNull).
  • Optional (non-required) fields exposed on the final BuildStep.
  • Must cooperate with existing features where feasible (With copy-and-modify, Jackson, access-level control, naming suffixes) or explicitly document interactions/limitations.
  • Consider whether it fits the generator-registry / component model or needs a separate generation path.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestfuture-ideaLonger-term idea, not scheduled work

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions