Files
asmhatreandClaude Sonnet 5 8d0efb0d4b Add openapi-versioning: springdoc-openapi 3.1.1 vs Spring Framework 7 API versioning
Companion module for "springdoc-openapi with Spring Boot 4.1: Generating,
Customising and Versioning Your API Spec". Reuses the ApiVersionConfigurer
setup from the versioning-mechanics companion project and tests what
springdoc-openapi 3.1.1 actually generates for a path with multiple
version-scoped handlers: a default oneOf-merged operation with an
arbitrary operationId, a working GroupedOpenApi + OpenApiCustomizer fix
that collapses it to one clean schema per version, and a check of the
officially-versioning-supported functional-endpoint path (springdoc
v3.0.2's "Add support for Spring Framework API Versioning with Functional
Endpoints"), which turns out to document only one of two registered
versions rather than either version separately.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01M1uZDXWkY1MnTfQgt4HMeQ
2026-09-17 19:31:42 +00:00

2.3 KiB

Why the version→schema map in the fix is hand-maintained

VersionedOpenApiConfig keeps a small, explicit Map<String, VersionSpec> from version string to schema name / operationId / summary, rather than deriving that mapping automatically from the running application. This is a deliberate limitation, not an oversight, and it's worth being honest about why: an OpenApiCustomizer (org.springdoc.core.customizers.OpenApiCustomizer) receives exactly one argument, the fully-built io.swagger.v3.oas.models.OpenAPI document — see javap org/springdoc/core/customizers/OpenApiCustomizer.class:

public interface org.springdoc.core.customizers.OpenApiCustomizer {
  public abstract void customise(io.swagger.v3.oas.models.OpenAPI);
}

By the time a customizer runs, springdoc has already folded the three HandlerMethods into one merged Operation (see the previous chapter) — the association between "this specific handler method" and "this specific version string" has already been discarded. An OperationCustomizer (org.springdoc.core.customizers.OperationCustomizer) does get a HandlerMethod argument per call, which looks more promising, but it still runs once per merged operation, not once per original handler — it cannot see the three original mappings separately either, because springdoc folds first and customizes second. There is no supported extension point in 3.1.1 that intercepts before the fold.

Reflectively pulling the version condition back out of Spring's own RequestMappingInfo (via RequestMappingHandlerMapping.getHandlerMethods()) would work at the Spring MVC layer — Framework 7 exposes it there in principle — but wiring that into springdoc's generation pipeline means matching springdoc's internal notion of "this operation" back to Spring's RequestMappingInfo by path and method, which is exactly the kind of implementation-detail-coupled reflection that breaks silently on the next springdoc minor version. A hand-maintained map keyed by the version strings you already wrote in your own @GetMapping(version = ...) annotations is three lines of boilerplate per version and it does not break when springdoc changes how it folds operations internally.