Skip to content

Add @Value.Builder record builder support to ImmutablesBuilderProvider#4006

Open
rlei wants to merge 4 commits into
mapstruct:mainfrom
rlei:feature/immutables-record-builder-support
Open

Add @Value.Builder record builder support to ImmutablesBuilderProvider#4006
rlei wants to merge 4 commits into
mapstruct:mainfrom
rlei:feature/immutables-record-builder-support

Conversation

@rlei
Copy link
Copy Markdown

@rlei rlei commented Feb 28, 2026

Problem

ImmutablesBuilderProvider supports @Value.Immutable on interfaces and abstract classes, where Immutables generates an ImmutableXxx implementation containing a nested Builder. However, since Immutables 2.11.0, Java records can be annotated with @Value.Builder, which causes Immutables to generate a top-level companion {RecordName}Builder class instead. The existing provider did not handle this case, causing MapStruct to fall back to calling the record constructor directly — bypassing the builder's mandatory-field enforcement.

Solution

  • Added findBuilderInfoForImmutablesRecord(): detects records annotated with @Value.Builder and looks up the companion {RecordName}Builder type.
  • Added asImmutableRecordBuilderElement(): constructs the companion builder's qualified name following the {RecordName}Builder convention.
  • Added findBuilderInfoFromCompanionBuilder(): constructs BuilderInfo directly using the companion class's public no-arg constructor (which Immutables always generates for @Value.Builder on records) and findBuildMethods() to locate build().
  • Added immutablesRecordBuilderTest integration test using Immutables 2.11.0, restricted to JDK compiler on Java 16+. Includes a @Nullable field and NullValueCheckStrategy.ALWAYS to demonstrate that the builder's mandatory-field check fires when MapStruct skips a null setter — behaviour that would be silently lost if MapStruct fell back to the record constructor.

Notes

  • ElementKind.RECORD was added in Java 16; the implementation uses typeElement.getKind().name() string comparison to remain compilable on older Java releases.
  • ImmutablesBuilderProvider is automatically activated when org.immutables.value.Value.Immutable is on the classpath (see AnnotationProcessorContext); @Value.Builder is part of the same artifact so no additional activation is needed.

@rlei
Copy link
Copy Markdown
Author

rlei commented Feb 28, 2026

Hi @filiphr could you please take a look at this one?

With minor updates, this will also support the another record builder generator (https://github.com/Randgalt/record-builder, see issue #3937).

When a Java record is annotated with Immutables' @Value.Builder
(supported since Immutables 2.11.0), the annotation processor generates
a companion {RecordName}Builder class (e.g. GolfPlayerBuilder for record
GolfPlayer) rather than the traditional Immutable{TypeName} implementation
used for interfaces and abstract classes.

The existing ImmutablesBuilderProvider only looked for @Value.Immutable
and the ImmutableXxx naming convention, so it could not detect builders
for @Value.Builder-annotated records.

This commit adds:
- findBuilderInfoForImmutablesRecord(): detects records annotated with
  @Value.Builder and looks up the companion {RecordName}Builder type
- asImmutableRecordBuilderElement(): constructs the qualified name of
  the generated builder following the {RecordName}Builder convention
- findBuilderInfoFromCompanionBuilder(): constructs BuilderInfo directly
  from the companion class using its public no-arg constructor as the
  builder creation method and findBuildMethods() to locate build().
  Immutables always generates a public no-arg constructor (not a static
  factory) for @Value.Builder on records, mirroring how
  DefaultBuilderProvider handles inner *Builder classes.
- immutablesRecordBuilderTest integration test that exercises end-to-end
  mapping through the Immutables-generated record builder using
  Immutables 2.11.0, registered in MavenIntegrationTest and restricted
  to JDK compiler on Java 16+. Tests include a @nullable field to show
  that the builder's mandatory-field enforcement fires correctly when
  NullValueCheckStrategy.ALWAYS causes MapStruct to skip setting a null
  mandatory field, whereas falling back to the constructor would silently
  accept null.

ElementKind.RECORD was added in Java 16; the implementation uses
typeElement.getKind().name() string comparison to remain compilable
on Java releases that pre-date records.
@rlei rlei force-pushed the feature/immutables-record-builder-support branch from 00c2f52 to 8037de2 Compare March 1, 2026 19:13
rlei added 2 commits March 6, 2026 20:24
Adds unit tests for the new ImmutablesBuilderProvider record-builder
support, covering the code paths not exercised by integration tests:

- Value.java: minimal stub of org.immutables.value.Value (with both
  @immutable and @builder inner annotations) so that the test
  compilation activates ImmutablesBuilderProvider as the default
  builder provider without requiring the Immutables jar
- GolfPlayer: a @Value.Builder-annotated record with a nullable Integer
  handicap field and mandatory name/age fields
- GolfPlayerBuilder: hand-crafted stand-in for the Immutables-generated
  companion builder, using an initBits bitmask to enforce mandatory
  fields at build() time
- ImmutablesRecordBuilderTest: three @ProcessorTest(Compiler.JDK) tests
  covering the happy path, nullable field handling, and mandatory-field
  enforcement via NullValueCheckStrategy.ALWAYS
…rProvider

Covers the findBuilderInfoForImmutables() code path where a @Value.Immutable
annotated interface is mapped through the ImmutableXxx generated
implementation and its nested Builder, improving instruction coverage
from 84% to 88%.
@rlei
Copy link
Copy Markdown
Author

rlei commented Mar 6, 2026

It seems the integration tests in the first commit was not considered in the coverage report.

@filiphr I've added unit tests to cover ImmutablesBuilderProviders, could you please check again?

Copy link
Copy Markdown
Member

@filiphr filiphr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding the tests in the processor module @rlei. I was going to merge it without it as well, but it's better to have them closer. However, I think that we should not add the @Value like you did. I've left some comments inline. If we address those then I'll go ahead and merge this.

Comment thread processor/src/test/java/org/immutables/value/Value.java Outdated
@rlei
Copy link
Copy Markdown
Author

rlei commented May 19, 2026

Hi @filiphr sorry for the late update - was away for some time.

I've updated the PR and replied to your comments, could you please review it again?

@rlei rlei requested a review from filiphr May 19, 2026 13:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants