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
3.5 KiB
What springdoc actually generates for a versioned path
Full transcript: docs/output/02-what-springdoc-actually-generates.txt,
produced by OpenApiVersioningTest#b_defaultApiDocsMergesAllVersionsIntoOneOf.
Hit the plain, ungrouped /v3/api-docs on this module — no custom configuration beyond adding
the starter — and here is the entire operation springdoc built for GET /accounts/{id}, which
has three handlers mapped to it (version = "1", "1.1+", "2"):
{
"operationId": "getAccountV2",
"parameters": [
{ "name": "id", "in": "path", "required": true, "schema": { "type": "string" } },
{
"name": "API-Version",
"in": "header",
"schema": {
"type": "string",
"default": "1.0.0",
"enum": ["2", "1", "1.0.0", "1.1"]
}
}
],
"responses": {
"200": {
"content": {
"*/*": {
"schema": {
"oneOf": [
{ "$ref": "#/components/schemas/AccountV1" },
{ "$ref": "#/components/schemas/AccountV2" },
{ "$ref": "#/components/schemas/AccountV1Point1" }
]
}
}
}
}
}
}
Three things worth naming precisely, since none of them are documented behaviour as far as I could find — this is what running it produced, not what any springdoc doc page promises:
One operation, not three. springdoc does not know that version is a routing discriminant
the way it knows @PathVariable or @RequestParam are. As far as its operation-building code is
concerned, three HandlerMethods map to the same path and HTTP method, so it folds them into one
Operation — the same folding it would do for, say, an overloaded controller method disambiguated
by a headers = "X-Foo" condition.
The response schema becomes oneOf all three shapes, with no discriminator. This is
technically not wrong — a client that doesn't send API-Version really could get any of the
three shapes back, so oneOf is a defensible schema for "the operation's response, considered
without reference to versioning." But it is unusable as version-specific documentation: nothing in
this schema says which shape maps to which version.
operationId is picked from whichever handler springdoc processes last, arbitrarily from the
reader's perspective. It happened to be getAccountV2 here. Generated API clients
(openapi-generator, openapi-typescript, etc.) use operationId as the method name they generate —
so a client generated from this spec gets one method, named after one version, that claims to
return oneOf three different shapes.
The auto-documented API-Version header enum is also slightly wrong on its own terms: it
lists "1" and "1.0.0" as if they were two different values, when "1.0.0" is just the
semantic-version-normalised form of the same default "1" configured in WebConfig
(setDefaultVersion("1")). Framework 7's version parser is doing exactly what it's supposed to —
1 and 1.0.0 really do compare equal — but springdoc surfaces both the raw configured string and
the normalised form as if a caller could sensibly choose between them.
None of this is a crash — the document is valid OpenAPI 3.1, it just isn't the document most teams actually want from a versioned API. The next chapter is a working fix for the first two problems; the header-enum oddity is cosmetic enough that I didn't chase it further, and is called out in 06-known-issues.md instead.