Skip to content

fix: manage the OpenAPI stack so Swagger cannot split into a NoSuchMethodError - #7

Merged
hendrikebbers merged 3 commits into
mainfrom
fix/003-openapi-stack-versions
Sep 10, 2026
Merged

hendrikebbers merged 3 commits into
mainfrom
fix/003-openapi-stack-versions

Conversation

@herbie-bot

Copy link
Copy Markdown

Summary

The parent managed springdoc-openapi-starter-webmvc-ui 2.8.17 but none of the
Swagger artifacts springdoc is built against, so Swagger floated and was resolved by
nearest-wins mediation in every consuming project. A project using this parent hit
the resulting failure and had to pin springdoc back to 2.8.6 to work around it.

The failure is inside Swagger, not between springdoc and Swagger

Worth stating precisely, because it changes what the fix has to cover. springdoc
2.8.17's compiled classes reference no Swagger member missing from 2.2.29 —
verified across all 285 classes of the three starters. springdoc is not the caller
that fails.

Swagger breaks itself. A library that uses only the Swagger annotations declares only
swagger-annotations-jakarta — the normal thing for such a library to do — and that
declaration sits one level from the application, beating the 2.2.47 springdoc
contributes two levels down via swagger-core-jakarta. swagger-core-jakarta itself
has no competitor and stays at 2.2.47. The stack splits along exactly the artifact
boundary a well-behaved library would touch, and then:

io.swagger.v3.core.jackson.ModelResolver.resolve$dynamicRef(...)   [core 2.2.47]
  invokeinterface io/swagger/v3/oas/annotations/media/Schema.$dynamicRef:()Ljava/lang/String;
                                                     added in annotations 2.2.47
                                                     absent in annotations 2.2.29

NoSuchMethodError. This is also why pinning a single artifact would not have been
enough — the three move as a unit.

Spec

  • Spec folder: docs/specs/003-openapi-stack-versions/
  • Design: docs/specs/003-openapi-stack-versions/design.md
  • Behaviors: docs/specs/003-openapi-stack-versions/behaviors.md
  • Steps and measured coverage: docs/specs/003-openapi-stack-versions/steps.md

Changes

  • swagger-bom import — the part that actually fixes the split. Covers all 14
    Swagger coordinates, so a future springdoc pulling in another Swagger module is
    covered without maintaining an artifact list.
  • springdoc-openapi-bom import — replaces the hand-managed single starter.
    It contributes nothing to the bug: it manages springdoc's own artifacts and
    deliberately says nothing about Swagger. It is imported so the vendor's list
    replaces ours, and children can use the webflux and scalar starters without a
    version.
  • org.webjars:swagger-ui managed — no BOM covers it and it belongs to the same
    coupled set.
  • Three coupled properties with the lockstep rule documented in the two places a
    maintainer would look: a comment on the properties themselves and a README
    maintenance section naming the exact POM and properties to read for a bump.

Rejected: importing org.springdoc:springdoc-openapi (the aggregator) would make the
lockstep mechanical, but it is springdoc's internal build structure rather than a
published contract, and it also manages jjwt, scalar and spring-cloud-function.

Verification

Measured against throwaway probe projects, then removed:

Probe Result
Pre-change parent, library contributing annotations 2.2.29 annotations 2.2.29 beside core 2.2.47 — the split reproduced
Modified parent, same graph annotations, models and core all 2.2.47
springdoc-openapi-starter-webflux-ui with no version resolves 2.8.17 — previously impossible
Consumer with a direct versioned swagger-annotations-jakarta 2.2.29 stays 2.2.29 — the documented limit of dependencyManagement
Same consumer with the <version> removed resolves 2.2.47
Child setting <swagger.version>2.2.50</swagger.version> all Swagger coordinates move to 2.2.50
Child declaring the javax swagger-annotations and swagger-jaxrs2-jakarta both resolve 2.2.47 — the BOM's wider reach, confirmed

15 of 18 scenarios measured. The counter-probe against the unmodified parent
exists so the check cannot pass vacuously.

Constraint found during implementation

io.swagger.core.v3:swagger-bom was first published at 2.2.47 — every earlier
version 404s, and the published range is 2.2.47 to 2.2.55. The version this change
needs is the very first one that has a BOM at all. Consequences, now recorded in the
POM comment, the design and the README: a springdoc release pinned to an older
Swagger could not use the import, and a child overriding swagger.version below
2.2.47 gets an explicit model-building failure rather than an override. Declaring the
artifact directly with a <version> is unaffected.

Known limits

  • A consumer that declares a Swagger artifact directly with a version still wins
    and is neither fixed nor warned. spring-services-core is the known case; tracked
    in docs/TODO.md.
  • The lockstep is documented, not enforced. Bumping springdoc without Swagger would
    publish a split stack to every child at once, and this parent's own build would not
    notice — it has no Java sources. Also tracked in docs/TODO.md.
  • The Swagger UI pin is preventive: the probe did not create a competing webjar
    version, so that entry is not demonstrated as load-bearing.

Note for the reviewer

This branch, feat/002-pinned-line-endings and feat/001-reproducible-build-timestamp
each add a README section and will conflict there on the second and third merge. The
conflicts are additive — all sections are kept.

Closes #6

🤖 Generated with Claude Code

hendrikebbers and others added 3 commits September 10, 2026 19:05
Links spec 003 to issue #6.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The parent managed springdoc-openapi-starter-webmvc-ui 2.8.17 but none of
the Swagger artifacts springdoc is built against, so Swagger floated and was
resolved by nearest-wins mediation in every consuming project.

The failure that motivated this is not springdoc calling a newer Swagger. It
is Swagger calling itself across a version boundary: a library that uses only
the annotations declares only swagger-annotations-jakarta, its declaration
beats the 2.2.47 springdoc contributes two levels down, and swagger-core
2.2.47 then executes

  invokeinterface io/swagger/v3/oas/annotations/media/Schema.$dynamicRef

against annotations 2.2.29, where that member does not exist. A consuming
project hit exactly this and had to pin springdoc back to 2.8.6.

- Import springdoc-openapi-bom, replacing the hand-managed single starter and
  making every springdoc starter usable without a version.
- Import swagger-bom, which is what actually fixes the split. The official
  springdoc BOM manages springdoc artifacts only and deliberately says
  nothing about Swagger.
- Manage org.webjars:swagger-ui, which no BOM covers and which belongs to the
  same coupled set.
- Document the lockstep at both places a maintainer would look: a comment on
  the properties and a README maintenance section naming the exact POM and
  properties to read for a future bump.

swagger-bom was first published at 2.2.47, so swagger.version has a floor.
That constraint was found during implementation and is recorded in the POM
comment and the README.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds the implementation steps and the coverage mapping, and folds the
measurements back into the spec: 15 of 18 scenarios were confirmed against
throwaway probe projects, including a counter-probe against the unmodified
parent so the check cannot pass vacuously.

Two findings changed the documents rather than being silently accepted. The
swagger-bom floor of 2.2.47 is now in design.md and behaviors.md, including
the consequence that a child overriding swagger.version downwards gets a
model-building failure. The Spring Boot overlap scenario now names what
spring-boot-dependencies 3.5.14 actually manages under org.webjars, so the
claim is checkable instead of merely asserted.

docs/TODO.md records the javax and JAX-RS reach of swagger-bom as confirmed
rather than suspected, and marks spec 003 done in the index.

Closes #6

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@hendrikebbers
hendrikebbers force-pushed the fix/003-openapi-stack-versions branch from 2199e73 to 0ccce73 Compare September 10, 2026 17:06
@hendrikebbers
hendrikebbers merged commit c082b01 into main Sep 10, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Manage the OpenAPI stack (springdoc, Swagger, Swagger UI) in dependencyManagement

2 participants