Skip to content

Extend TypeNotPresentException probing to nested annotations - #37157

Open
junhyeong9812 wants to merge 1 commit into
spring-projects:mainfrom
junhyeong9812:fix/attributemethods-nested-annotation-probe
Open

Extend TypeNotPresentException probing to nested annotations#37157
junhyeong9812 wants to merge 1 commit into
spring-projects:mainfrom
junhyeong9812:fix/attributemethods-nested-annotation-probe

Conversation

@junhyeong9812

Copy link
Copy Markdown
Contributor

Overview

AttributeMethods probes Class, Class[], and enum attributes so that annotations whose values cannot be resolved at runtime are filtered during scanning instead of failing later. Annotation-typed attributes are not probed, and probing them by invocation alone would not be sufficient: the JDK returns the nested annotation proxy successfully and only throws when one of the nested annotation's own attributes is accessed.

Problem

An annotation whose nested annotation member references a constant or type that no longer exists at runtime (a binary-incompatible classpath, for example partially upgraded dependencies) passes canLoad() unprobed:

  • MergedAnnotations reports the annotation as present, while the same corruption in a direct attribute is filtered with a warning log.
  • asMap() returns the corrupted nested annotation proxy as an attribute value, deferring the failure into AnnotationAttributes consumers.
  • Typed or synthesized access throws a raw EnumConstantNotPresentException from deep inside scanning or user code.

Fix

canLoad() and validate() now capture the probed value and recurse into annotation and annotation array attribute values, applying the same probing to the nested annotation's own attributes. The probe flag computation marks annotation-typed attributes, reusing the adjacent nested-annotation detection idiom. Recursion terminates structurally because the JLS forbids cyclic annotation member types.

Behavior notes:

  • A corrupted nested member now hides the entire outer annotation from scanning, matching the existing all-or-nothing semantics for direct Class and enum attributes ("true if all values are present"). The warning log names the actual failing annotation.
  • validate() propagates the IllegalStateException raised for the failing nested attribute without re-wrapping, so the message identifies the innermost failing annotation.
  • Annotations whose nested annotation type itself is missing from the classpath fail earlier, during getDeclaredAnnotations() parsing, and are out of scope for this probing.

Cost, measured with a canLoad() microbenchmark (before/after, best of 5 x 500k iterations): existing probes are unchanged (enum attribute 25ns before and after); annotation-typed attributes add roughly 50-70ns per canLoad() invocation, dominated by the added reflective read; the scanner's per-element caching bounds repetition on regular Class/Member sources.

Tests cover the probe flag for annotation and annotation array attributes, canLoad() and validate() for failing and healthy nested annotations, annotation arrays including the empty array, and two-level nesting; the failure assertions pin the exception message and cause to the innermost annotation.

This change is complementary to #37153 (enum array attributes) and overlaps with it only on the flag computation line.

AttributeMethods probes Class, Class[], and enum attributes so that
annotations whose values cannot be resolved at runtime are filtered
during scanning. Annotation-typed attributes were not probed, and
probing them by invocation alone is not sufficient: the JDK returns
the nested annotation proxy successfully and only throws when one of
its own attributes is accessed. An annotation whose nested annotation
references a stale enum constant therefore passed canLoad() and leaked
EnumConstantNotPresentException (or the exception instance itself via
attribute maps) on later access.

canLoad() and validate() now recurse into annotation and annotation
array attribute values, reusing the same probing for the nested
annotation's own attributes. Recursion is guaranteed to terminate
because the JLS forbids cyclic annotation member types.

Signed-off-by: junhyeong9812 <pickjog@gmail.com>
@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Aug 19, 2026
@sbrannen sbrannen self-assigned this Aug 19, 2026
@sbrannen sbrannen added the in: core Issues in core modules (aop, beans, core, context, expression) label Aug 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

in: core Issues in core modules (aop, beans, core, context, expression) status: waiting-for-triage An issue we've not yet triaged or decided on

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants