You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Merged composed-annotation values for the callers that still read declared values #672
#651 added one resolver, MergedAnnotationAttributes, that reads composed annotations the way Spring merges them: @AliasFor defaults, convention-named overrides before Spring 7, and the nearest meta-annotation chain. Its PR (#673) switched only the UAST API MetaAnnotationsHolder.getAnnotationMemberValues(UAnnotation, …) to merged values. The deprecated PSI overloads still return the values written at the use site, so these callers still ignore alias defaults of composed annotations.
Callers that still read declared values only
Class-level controller prefixes:SpringWebControllerLoader, HandlerMethods. A composed @ApiController whose path alias defaults to /api gives the prefix "", while the method-level mapping under it is already merged.
Client and JAX-RS loaders:SpringHttpExchangeLoader, SpringWebFeignClientLoader, JaxRsExchangeLoader.
Request mappings on classes and methods are merged (AnnotatedElementUtils.findMergedAnnotation in RequestMappingHandlerMapping).
Also seen in review
Spring 6 AnnotationTypeMapping.addConventionAnnotationValues: an intermediate meta-annotation's same-named, unaliased attribute overrides a deeper one (@O → @C2(mode = "M") → @Tgt(mode = "T")). The resolver answers T; probe it on 6.2.x and 7.0.x.
Each composed use site rebuilds the meta-path and alias sets; cache the per-type data in the module-cached holder.
SpringWebUtil.requestMappingOf answers path "" and all verbs when the UAST conversion fails; fall back to the declared PSI values instead.
PR #690 covers the first caller group: class-level @RequestMapping prefixes of composed controller annotations.
HandlerMethods.requestMappingPrefixes reads the class mapping merged (fix: Read composed annotations as Spring merges them (#651) #673): @AliasFor defaults and use-site values, an alias to value, a second composed level, a meta-fixed path; an unaliased same-named attribute only when the Spring major is known and below 7.
Spring keeps only the first @RequestMapping descriptor ordered by meta-distance (RequestMappingHandlerMapping.createRequestMappingInfo, spring-webmvc 6.2.19 :352-361, "only the first will be used"). The prefix now comes from exactly that annotation: a direct @RequestMapping wins over a composed one in any source order.
SpringWebControllerLoader reuses requestMappingPrefixes, so the Endpoints tool window, the MCP endpoint tools, the run gutters and "Generate OpenApi Specification" get the same prefix.
Remaining groups, in the order I plan them (line refs on main7d9574f):
Method-level @RequestMapping readers on the declared PSI overload: ControllerEndpointActionsLineMarkerProvider.kt:56-71, RequestMappingDuplicateInspection.kt:80,108,147,179, plus SpringWebUtil.requestMappingOf (SpringWebUtil.kt:390-394), which merges values of every mapping annotation of a handler method, while Spring keeps only the nearest one (same createRequestMappingInfo rule).
@HttpExchange: SpringHttpExchangeLoader.kt:71,80,89. Spring reads it merged (HttpServiceMethod, spring-web 6.2.19 :329, MergedAnnotations.from(element, TYPE_HIERARCHY, …)).
Conditions: ConditionalOnBeanStrategy.kt:29-40, ConditionalOnMissingBeanStrategy.kt:29-40, PropertyConditionSpecReader.kt:52-55. Boot reads @ConditionalOnProperty through metadata.getAnnotations() (merged, OnPropertyCondition 3.5.15 :55) and OnBeanCondition works on MergedAnnotations; each one gets a probe on real jars before it is switched.
OpenFeign (SpringWebFeignClientLoader.kt:68-118): not verified yet; the OpenFeign sources are not at hand.
Dropped from scope:
Request parameter requiredness: @RequestParam, @RequestHeader, @PathVariable, @RequestBody and @CookieValue are @Target(ElementType.PARAMETER) only (spring-web 6.2.19), so they cannot sit on an annotation type and cannot be composed; declared values are the merged ones.
JAX-RS @Path (JaxRsExchangeLoader.kt:113-126): a JAX-RS runtime reads the annotation as declared on the resource class or method, without Spring's merged-annotation semantics, so there is nothing to merge.
Group 2 (method-level `@RequestMapping` readers) is merged as `e73a5a7fc` (PR #707).
`HandlerMethods.methodMapping` is now the single reader for a method's mapping: it picks the one annotation Spring registers (a direct `@RequestMapping` first, else the nearest composed one, ties by source order) and takes path, verbs, `produces` and `consumes` from that winner only, with merged values and a declared-PSI fallback. `SpringWebUtil.requestMappingOf` and `getEndpointInfo`, both endpoint gutters, `RequestMappingDuplicateInspection` and the MCP `SpringMcpProvider.mediaTypesOf` all delegate to it, so the model, the gutters, OpenAPI generation and the MCP tools agree by construction.
Probe behind it (Spring 6.2.19 and 7.0.9, MVC and WebFlux identical): a direct `@RequestMapping` beats a composed one in both source orders; two composed annotations at the same meta-distance resolve to the first in source order; a convention-named unaliased `path` applies on 6.x and is dropped on 7.x.
Two things rode along: a declared but unreadable path (`@GetMapping(SOME_UNRESOLVED_CONST)`) no longer becomes a phantom row at the class prefix, and the private `RequestMappingVisitor` in `RequestMappingDuplicateInspection` was deleted — it had no caller and never had one.
Still open under this issue: method-level `@HttpExchange`, `@ConditionalOnBean`/`@ConditionalOnMissingBean` together with `PropertyConditionSpecReader` (probe first — each Spring condition decides merged vs declared on its own), and `OnWebApplicationCondition`.
Known gaps worth their own issues, named in the PR: the PSI fallback reads declared values, so a compiled library mapping source still loses an `@AliasFor` default; a verb given through a constant (`method = M`) is named after the constant; and the gutters still fall back to the class prefix for an unreadable path, as they did before. An inspection for "several mapping annotations on one element, only the first is used" is also worth filing — Spring logs exactly that warning.
Activity
imuromtsev commentedon Oct 13, 2026
PR #690 covers the first caller group: class-level
@RequestMappingprefixes of composed controller annotations.HandlerMethods.requestMappingPrefixesreads the class mapping merged (fix: Read composed annotations as Spring merges them (#651) #673):@AliasFordefaults and use-site values, an alias tovalue, a second composed level, a meta-fixed path; an unaliased same-named attribute only when the Spring major is known and below 7.@RequestMappingdescriptor ordered by meta-distance (RequestMappingHandlerMapping.createRequestMappingInfo, spring-webmvc 6.2.19 :352-361, "only the first will be used"). The prefix now comes from exactly that annotation: a direct@RequestMappingwins over a composed one in any source order.SpringWebControllerLoaderreusesrequestMappingPrefixes, so the Endpoints tool window, the MCP endpoint tools, the run gutters and "Generate OpenApi Specification" get the same prefix.Remaining groups, in the order I plan them (line refs on
main7d9574f):@RequestMappingreaders on the declared PSI overload:ControllerEndpointActionsLineMarkerProvider.kt:56-71,RequestMappingDuplicateInspection.kt:80,108,147,179, plusSpringWebUtil.requestMappingOf(SpringWebUtil.kt:390-394), which merges values of every mapping annotation of a handler method, while Spring keeps only the nearest one (samecreateRequestMappingInforule).@HttpExchange:SpringHttpExchangeLoader.kt:71,80,89. Spring reads it merged (HttpServiceMethod, spring-web 6.2.19 :329,MergedAnnotations.from(element, TYPE_HIERARCHY, …)).ConditionalOnBeanStrategy.kt:29-40,ConditionalOnMissingBeanStrategy.kt:29-40,PropertyConditionSpecReader.kt:52-55. Boot reads@ConditionalOnPropertythroughmetadata.getAnnotations()(merged,OnPropertyCondition3.5.15 :55) andOnBeanConditionworks onMergedAnnotations; each one gets a probe on real jars before it is switched.OnWebApplicationConditionStrategy.kt:34: Boot readsmetadata.getAnnotationAttributes(ConditionalOnWebApplication)(3.5.15 :160), merged; probe first.SpringWebFeignClientLoader.kt:68-118): not verified yet; the OpenFeign sources are not at hand.Dropped from scope:
@RequestParam,@RequestHeader,@PathVariable,@RequestBodyand@CookieValueare@Target(ElementType.PARAMETER)only (spring-web 6.2.19), so they cannot sit on an annotation type and cannot be composed; declared values are the merged ones.@Path(JaxRsExchangeLoader.kt:113-126): a JAX-RS runtime reads the annotation as declared on the resource class or method, without Spring's merged-annotation semantics, so there is nothing to merge.fix: Read class-level prefixes of composed controller annotations as …
fix: Read method request mappings from the single annotation Spring r…
imuromtsev commentedon Oct 15, 2026
Group 2 (method-level `@RequestMapping` readers) is merged as `e73a5a7fc` (PR #707).
`HandlerMethods.methodMapping` is now the single reader for a method's mapping: it picks the one annotation Spring registers (a direct `@RequestMapping` first, else the nearest composed one, ties by source order) and takes path, verbs, `produces` and `consumes` from that winner only, with merged values and a declared-PSI fallback. `SpringWebUtil.requestMappingOf` and `getEndpointInfo`, both endpoint gutters, `RequestMappingDuplicateInspection` and the MCP `SpringMcpProvider.mediaTypesOf` all delegate to it, so the model, the gutters, OpenAPI generation and the MCP tools agree by construction.
Probe behind it (Spring 6.2.19 and 7.0.9, MVC and WebFlux identical): a direct `@RequestMapping` beats a composed one in both source orders; two composed annotations at the same meta-distance resolve to the first in source order; a convention-named unaliased `path` applies on 6.x and is dropped on 7.x.
Two things rode along: a declared but unreadable path (`@GetMapping(SOME_UNRESOLVED_CONST)`) no longer becomes a phantom row at the class prefix, and the private `RequestMappingVisitor` in `RequestMappingDuplicateInspection` was deleted — it had no caller and never had one.
Still open under this issue: method-level `@HttpExchange`, `@ConditionalOnBean`/`@ConditionalOnMissingBean` together with `PropertyConditionSpecReader` (probe first — each Spring condition decides merged vs declared on its own), and `OnWebApplicationCondition`.
Known gaps worth their own issues, named in the PR: the PSI fallback reads declared values, so a compiled library mapping source still loses an `@AliasFor` default; a verb given through a constant (`method = M`) is named after the constant; and the gutters still fall back to the class prefix for an unreadable path, as they did before. An inspection for "several mapping annotations on one element, only the first is used" is also worth filing — Spring logs exactly that warning.