Skip to content

fix: Read composed, inherited and conditional Actuator web extensions as Boot discovers them (#655) - #691

Merged
imuromtsev merged 2 commits into
mainfrom
imuromtsev/composed-endpoint-extension
Oct 14, 2026
Merged

imuromtsev merged 2 commits into
mainfrom
imuromtsev/composed-endpoint-extension

Conversation

@imuromtsev

Copy link
Copy Markdown
Collaborator

Purpose

Boot decides which endpoint a web extension extends from the extension bean's type hierarchy with merged annotation values (EndpointDiscoverer.ExtensionBean: MergedAnnotations.from(beanType, TYPE_HIERARCHY).get(EndpointExtension.class).getClass("endpoint")), and it only uses extension beans whose conditions match. The plugin read endpoint from the extension class's own use-site annotation only and ignored the extension's bean condition, so composed, aliased and inherited extensions were dropped, and a disabled extension replaced the endpoint's operations. Items 5 and 3 of #655.

Change

  • Item 5, declaration. The extension's endpoint is read through the merged resolver of fix: Read composed annotations as Spring merges them (#651) #673, rooted at EndpointExtension: a value fixed on a composed meta-annotation, an @AliasFor to EndpointWebExtension.endpoint (its default or a use-site value takes precedence over the meta value), and an @AliasFor straight to EndpointExtension.endpoint. An unaliased attribute named endpoint counts only up to Spring Framework 6 (convention mapping), as in fix: Read composed annotations as Spring merges them (#651) #673.
  • The annotation is looked up over the type hierarchy in Spring's order (the class, its interfaces, then the superclass), so an annotation declared on an abstract base class or an interface applies to its concrete subclasses; only concrete classes count as extensions.
  • Item 3, conditions. Extension classes get bean-condition verdicts like endpoints: an extension whose condition does not match is dropped before its operations are merged (the endpoint keeps its own operation); an extension whose condition cannot be decided is applied, and the endpoint's beanCondition becomes undecided with the extension as carrier.

Framework evidence

Real discovery on Boot 3.5.15 (Spring 6.2) and 4.1.0 (Spring 7.0), endpoints X and Y, the extension adds an operation:

Extension declaration 3.5.15 4.1.0
composed annotation, meta @EndpointWebExtension(endpoint = X) extends X extends X
@AliasFor to EndpointWebExtension.endpoint, default X, meta value Y extends X extends X
same alias, default Y, use site X extends X extends X
@AliasFor to EndpointExtension.endpoint, default / use site X extends X extends X
unaliased endpoint() default X, meta value Y extends X extends Y
direct annotation on a superclass only extends X extends X
composed annotation on a superclass only extends X extends X

Not in this PR

  • Item 4: an inspection for two extensions of one endpoint (Boot fails at startup with "Found multiple extensions"), stacked next on this branch. Until then the model keeps the operations of one of them.
  • Item 2: extensions declared in libraries (deferred: no built-in endpoint depends on an extension only).
  • The MCP beanCondition description does not yet say that the carrier can be an extension (spring-ai wording, follow-up).

Verification

  • Red commit 2863b94: 20 of 52 tests failed (composed, aliased in Kotlin, convention per Spring major, superclass-declared, and the property-gated extension: missing, false, unresolvable), each on the served operation or the beanCondition assertion.
  • After the fix 3ed4710: the 52 tests pass; all 22 Actuator test classes of spring-web pass (193 tests).
  • All modules (:spring-bootstrap:test --rerun-tasks on 3ed4710, fresh JUnit XML): spring-core 2387, spring-web 937 (6 skipped), spring-ai 776, jpa 138, spring-data 140, base 35, aop 6, security 6, initializr 5; 0 failures, 0 errors.

Main only: ActuatorEndpointLoader and MergedAnnotationAttributes are not on the release branches.

Refs #655

@github-actions

Copy link
Copy Markdown
TestsPassed ✅Skipped ⚠️Failed
JUnit Test Report4430 ran4424 passed6 skipped0 failed

@imuromtsev
imuromtsev merged commit 2a6732c into main Oct 14, 2026
2 checks passed
@imuromtsev
imuromtsev deleted the imuromtsev/composed-endpoint-extension branch October 14, 2026 06:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

actuator Spring Boot Actuator endpoints and management.* keys in:spring-web

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant