Conversation
A record component's annotations go to whichever of the component, the field,
the accessor and the constructor parameter each one applies to (JLS 9.7.4).
`Symbol.RecordComponent#getAnnotationMirrors()` lists only the ones that reached
the component, while `#getOriginalAnnos()` lists everything written in the
header, and `extractRecordComponentAnnotations` walks the two together by index.
Where the lists diverge the attribution slides: an annotation applicable only to
the accessor never gets a type, and when one precedes a component-applicable
annotation it takes that annotation's type instead. `@JsonGetter`, targeting
`{ANNOTATION_TYPE, METHOD}`, is enough to reach both.
The four cases are disabled pending a fix; the component-applicable case passes
today and guards against regressing it.
2 of 4 tasks
A record component's annotations are propagated to whichever of the component,
the field, the accessor and the canonical constructor parameter each one applies
to (JLS 9.7.4), and javac attributes each one only where it landed. The trees in
`getOriginalAnnos()`, the ones written in the header, it leaves untyped.
`extractRecordComponentAnnotations` walked those together with
`getAnnotationMirrors()` by index, but the mirrors cover only the
component-applicable annotations, so the two line up only when every annotation
in the header applies to the component. Where they diverge an annotation loses
its type, or worse, takes the neighbouring one's.
Take each type from where javac left it instead: from the accessor's copy in
`RecordComponent#accessorMeth`, which sits at the same source position, and
failing that from the component's mirrors, matched by name. Repeated annotations
are attributed as their container, so its elements are candidates too. The
field's and the constructor parameter's copies were already merged over the
originals by position.
The accessor's copies are read for their type alone rather than merged into the
position table the way the field's and the parameter's are: they are javac's
desugared trees, spelling `@A("x")` as `@A(value = "x")` and carrying no end
positions.
`@OnMethod(value = "full_name")` in a record header only parses since #8724: `getOriginalAnnos()` hands back copies that are absent from the end position table, so `endPos(arg) < 0` holds for every annotation written there and a written attribute name was taken for an elided one.
Type validation cannot see the failure this guards: an annotation that takes its neighbour's type is well formed. `@OnMethod @OnComponentAndField` is the shape where nothing is left untyped to give it away, since the field's attributed copy supplies a type for the annotation the mirror was shifted off. Against the index walk it parses to `OnMethod -> OnComponentAndField` with validation green. `componentApplicableTypes` collected every candidate up front, including the elements of a repeated annotation's container, which only a repeated annotation ever matches. Match against the mirrors directly and walk into a container only once nothing has matched, which drops the intermediate list.
MBoegers
marked this pull request as ready for review
September 3, 2026 08:14
MBoegers
requested review from
jkschneider and
sambsnyd
and removed request for
jkschneider and
sambsnyd
September 9, 2026 16:06
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Annotated record components are around now. Jackson DTOs, Spring request bodies, validation constraints. Since v8.69.0 the parser has been giving those annotations the wrong type, or no type at all.
Two ways to hit:
TypeValidation.none()usageSolution
The parser now takes each annotation's type from wherever javac actually recorded it, instead of assuming the annotations written in the record header line up with the ones that reached the component. They are distributed by javac.