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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

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

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions