Skip to main content

ADR-0012: springdoc-generated OpenAPI as the single API contract

Status: Accepted (retroactive); amended by ADR-0017 on 2026-08-04 Date: 2026-07-24

Amendment (ADR-0017). Two details below no longer hold. Configuration is no longer YAML-only: OpenApiConfiguration.java now supplies the document metadata, the bearerAuth scheme and nine GroupedOpenApi groups. And the contract is no longer runtime-only — CI renders it into the public API reference at docs.motorphenterprise.com/api, which closes the "no self-serve production API docs" gap recorded under Negative. Everything else — the generator as the single source of the contract, and SWAGGER_ENABLED off in production — still stands.

Context

With 78 controllers and an API surface that changes weekly, any hand-maintained contract artifact — a Postman collection, a wiki page, a checked-in OpenAPI YAML — would drift from the code within days. The team is small; nobody's job is "keep the API docs current". The contract had to be a build artifact of the code itself, or it would be wrong.

At the same time, production stacks are client-facing (ADR-0007), and an always-on Swagger UI there would advertise the entire attack surface.

Decision

  • springdoc-openapi generates the contract from the code. The dependency is springdoc-openapi-starter-webmvc-ui (backend/pom.xml); configuration is YAML-only — the springdoc: block in application.yml is the entire setup; there is no OpenAPIConfig Java class in the codebase. Endpoint documentation comes from springdoc's inference plus io.swagger.v3 annotations, which 68 of the 78 controllers carry.
  • One gate, SWAGGER_ENABLED, controls both /v3/api-docs and /swagger-ui.html. Default on (dev/demo), and explicitly "false" in the production client template (docker-compose.client.template.yml), with an in-file comment: the docs expose the full API surface, keep them off client deployments.
  • No hand-maintained Postman collection. Consumers who want one import /v3/api-docs from a running dev stack into Postman/Insomnia — the generated document is the collection. Prose API docs in docs/ explain semantics and conventions but never enumerate endpoints exhaustively (that's the generator's job — see docs/README.md, "API").

Consequences

Positive

  • The contract cannot drift from the code: every merged controller change is in the next /v3/api-docs automatically, with zero maintenance work.
  • One switch cleanly separates "explorable dev API" from "quiet production API".
  • Tooling-friendly: Postman, Insomnia, and client-side codegen all consume the standard OpenAPI JSON.

Negative

  • Contract quality is only as good as annotation hygiene. springdoc guarantees presence, not quality: unannotated endpoints (10 of 78 controllers today) surface with inferred schemas, no descriptions, and no documented error shapes — and nothing fails the build for it.
  • No versioned request collections: there is no curated "golden requests" set with example payloads and environments; every consumer rebuilds their own from the schema, and there is no diffable contract history across releases (the contract exists only at runtime).
  • Swagger off in prod means no self-serve production API docs. A client integrating against their own instance must be handed docs from a dev stack — acceptable today (integrations are unsold), but a real gap if API access ever becomes a product feature.

References