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.javanow supplies the document metadata, thebearerAuthscheme and nineGroupedOpenApigroups. 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, andSWAGGER_ENABLEDoff 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 — thespringdoc:block inapplication.ymlis the entire setup; there is noOpenAPIConfigJava class in the codebase. Endpoint documentation comes from springdoc's inference plusio.swagger.v3annotations, which 68 of the 78 controllers carry. - One gate,
SWAGGER_ENABLED, controls both/v3/api-docsand/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-docsfrom a running dev stack into Postman/Insomnia — the generated document is the collection. Prose API docs indocs/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-docsautomatically, 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
../../backend/src/main/resources/application.yml— thespringdoc:block andSWAGGER_ENABLEDgate../../backend/pom.xml— springdoc dependency../../docker-compose.client.template.yml—SWAGGER_ENABLED: "false"in production../api/README.md— the Swagger-first API doc set this decision produced