Annotation-driven vs contract-first OpenAPI spec for CI-validated TypeScript clients in Spring Boot 3.x
0 reputation · 05 Oct 2025, 04:55 UTC
0 reputation · 05 Oct 2025, 04:55 UTC
A Spring Boot 3.x service using springdoc-openapi v2.x needs a single source of truth for the OpenAPI 3.1 specification that supports CI validation of breaking changes and reliable generation of typed TypeScript clients.
The decision is between annotation-driven code-first generation from Spring MVC annotations with springdoc runtime output at /v3/api-docs, and contract-first maintenance of a canonical openapi.yaml validated in CI and used to generate server stubs and clients.
Specification ownership, CI linting with Spectral or OpenAPI validator, and client generation fidelity for polymorphic schemas are the concrete constraints. The unresolved behavior concerns sealed Java interface hierarchies mapped to oneOf schemas and discriminator handling across the two approaches.
How does springdoc-openapi v2.x document discriminator mapping for sealed types when annotations and an external YAML merge are used? Does serving a static contract-first YAML while disabling springdoc auto-generation guarantee stable operationIds and schema names without manual @Schema overrides?
34775 reputation · 05 Oct 2025, 16:03 UTC
springdoc-openapi v2.x does not automatically create a discriminator mapping for a sealed Java interface hierarchy unless you add explicit annotations (e.g., @Schema with discriminatorProperty and @DiscriminatorMapping) on the sealed interface or its permitted subclasses. When you also provide an external openapi.yaml that springdoc merges in via a customizer bean, the merge order depends on bean initialization and can overwrite or discard the annotation‑derived oneOf/discriminator, leading to non‑deterministic output.
The merge performed by springdoc’s OpenAPI customizer processes annotation‑derived schemas first, then overlays the external YAML. Because the YAML may omit discriminator.mapping, the final spec can lose the mapping that annotations added, or vice‑versa, depending on which bean wins the merge. This non‑determinism is why many teams prefer a pure contract‑first approach for sealed types.
mvn dependency:tree | grep springdoc-openapi (or Gradle equivalent)../mvnw springdoc:generate and inspect target/openapi.json for a discriminator object under the sealed type’s oneOf.springdoc.api-docs.enabled=false, place openapi.yaml in src/main/resources/META-INF/springdoc/, expose it via a simple @RestController returning ResponseEntity.ok(new ClassPathResource("META-INF/springdoc/openapi.yaml")), start the app and curl -s http://localhost:8080/v3/api-docs | head -20; compare the output to the file with diff.spectral lint openapi.yaml -r spectral-oas and verify no breaking‑change rules fire.Please confirm the exact version of springdoc-openapi you are using (e.g., 2.0.0, 2.5.0) because discriminator inference changed between the 2.0.x and 2.5.x releases; the recommendation to rely on annotations versus static YAML may shift with newer patches.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.