Skip to content

#2932 Use builder for generic target types - #4133

Open
seonwooj0810 wants to merge 1 commit into
mapstruct:mainfrom
seonwooj0810:fix/issue-2932-generic-builder
Open

seonwooj0810 wants to merge 1 commit into
mapstruct:mainfrom
seonwooj0810:fix/issue-2932-generic-builder

Conversation

@seonwooj0810

Copy link
Copy Markdown
Contributor

Fixes #2932

Root cause

DefaultBuilderProvider#isBuildMethod checks that the builder's build method returns the type being built via typeUtils.isAssignable( buildMethodReturnType, typeElement.asType() ). For a generic type such as Lombok's @Builder on Record<T>, the build method returns Record<T> where T is the builder's type variable, while typeElement.asType() is Record<T> with the class's own T. These are distinct type variables, so the check fails for every generic type, no builder is found, and MapStruct falls back to the (private) all-args constructor — exactly the behaviour reported in the issue.

Change

  • DefaultBuilderProvider#isBuildMethod: compare the erasures of the build method's return type and the type being built.
  • BuilderType#create: resolve the builder's type parameters against the concrete target type, so that for Record<String> the generated code uses RecordBuilder<String> (not a raw/unresolved RecordBuilder<T>). If the parameters can't be resolved, the builder type is left unchanged.

Tests

Added processor/src/test/java/org/mapstruct/ap/test/bugs/_2932/ covering both builder shapes:

  • shouldUseBuilderOfGenericTarget — Record<T> with a static builder() factory (mirrors what Lombok generates for @Builder on a generic class).
  • shouldUseConstructorCreatedBuilderOfGenericTarget — Pair<K, V> whose builder is created through its public constructor.

Both fail on current main (4/4 failures across the JDK and Eclipse compilers) and pass with the fix.

Verification done:

  1. No in-flight PR or linked branch for Builder not Invoked for Generic Classes #2932 (gh pr list --search, issue has no linked PRs).
  2. No active claim on the issue (unassigned, no comments).
  3. Code-focused fix (.java only).
  4. Reproduced on current main (211b2be) by running the new test against the pre-fix code — all 4 iterations fail; they pass after the fix.
  5. No related open epic/tracking issue found.
  6. ./mvnw -pl processor -am clean install: all 3559 processor tests pass (0 failures, 0 errors) and checkstyle passes. (Locally on JDK 25 the animal-sniffer check reports TypeKind.MODULE in the untouched Type.java; this is unrelated to this change.)

DefaultBuilderProvider#isBuildMethod checked the build method's return type
(e.g. Record<T> with the builder's T) for assignability against the type
element's declared type (Record<T> with the class's own T). The two type
variables are distinct, so the check failed for every generic type and
MapStruct fell back to the (often private) constructor. Compare erasures
instead, and resolve the builder's type parameters against the concrete
target type (RecordBuilder<String> for Record<String>) in BuilderType.
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.

Builder not Invoked for Generic Classes

1 participant