Overview
The JVM is the home turf of annotation-driven code-first documentation: every stack can
decorate the code it already has (@Operation, @Schema, MicroProfile OpenAPI
annotations) and emit the spec at build or run time, which is why
generate-doc-from-code is the Java default.
Contract-first lives one layer up, in the build: OpenAPI Generator's Maven/Gradle plugins
turn a hand-authored spec into Spring interfaces, JAX-RS stubs or typed clients. This note
maps each doc flavor onto Spring, Quarkus and Micronaut.
Key points
- OpenAPI code-first is one dependency per stack: springdoc-openapi (Spring), quarkus-smallrye-openapi (Quarkus, MicroProfile OpenAPI), micronaut-openapi (compile time). Each serves the document and a Swagger UI out of the box.
- OpenAPI contract-first is generator-shaped: OpenAPI Generator's
springtarget (interfaces + delegate pattern your controllers implement),jaxrs-specfor Quarkus-style resources, andjava-micronaut-server; Quarkus adds quarkus-openapi-generator for build-time typed clients from a spec. - AsyncAPI has one real code-first citizen: Springwolf — scans Spring Kafka/AMQP/JMS listeners and produces an AsyncAPI document plus UI, springdoc-style. Elsewhere it is contract-first or nothing: AsyncAPI Generator templates for Spring, hand-authored specs validated in CI for Quarkus/Micronaut.
- gRPC is contract-first by construction:
.proto+ protobuf-maven-plugin or buf → grpc-java stubs, wrapped by Spring gRPC, quarkus-grpc (Mutiny stubs at build time) and micronaut-grpc; human-readable docs come from protoc-gen-doc or the Buf Schema Registry, never hand-written. - GraphQL splits by stack: Spring for GraphQL and Netflix DGS are schema-first (the
SDL in
resources/graphql/is the doc); SmallRye GraphQL (Quarkus) is code-first — annotations generate the schema, served at/graphql/schema.graphql; Micronaut wraps graphql-java and leaves the choice open, commonly schema-first. - Micronaut's twist: its OpenAPI support runs in the annotation processor — the spec is a build artifact with zero runtime cost, consistent with its compile-time ethos.
- CI belongs to the spec: whichever direction, lint with Spectral, diff with oasdiff
(or
buf lint/buf breakingfor proto) and publish the rendered doc from the same pipeline — see the trade-off note for why.
Details
Flavor × stack matrix
| Flavor | Spring | Quarkus | Micronaut |
|---|---|---|---|
| OpenAPI, code-first | springdoc-openapi: runtime scan of MVC/WebFlux annotations → /v3/api-docs + Swagger UI; enrich with swagger-core @Operation/@Schema |
quarkus-smallrye-openapi: MicroProfile OpenAPI annotations → /q/openapi + Dev UI Swagger; auto-augmented from Jakarta REST metadata at build time |
micronaut-openapi: annotation processor emits the spec at compile time; swagger-ui/redoc rendering via config |
| OpenAPI, contract-first | OpenAPI Generator spring target — API interfaces + delegate pattern; controllers implement, drift is a compile error |
OpenAPI Generator jaxrs-spec; quarkus-openapi-generator for typed REST clients generated during augmentation |
OpenAPI Generator java-micronaut-server / -client targets |
| AsyncAPI | Springwolf (code-first from @KafkaListener/@RabbitListener + UI); AsyncAPI Generator java-spring template for contract-first |
No first-class extension: hand-author the spec, validate with AsyncAPI CLI in CI; payload contracts via Apicurio/Avro registry | Same as Quarkus — spec by hand, registry for payloads |
| gRPC docs | .proto is the contract: protobuf-maven-plugin or buf → grpc-java; Spring gRPC starter wires servers/clients |
quarkus-grpc generates stubs at build; Dev UI lists services | micronaut-grpc on the same codegen |
| GraphQL | Spring for GraphQL: schema-first — SDL files are the source, @SchemaMapping binds resolvers; GraphiQL built in |
SmallRye GraphQL: code-first — @GraphQLApi annotations → SDL served by the app |
micronaut-graphql: bring graphql-java either way; commonly schema-first |
Choosing a direction on the JVM
Code-first is the path of least resistance everywhere above, and with the build-time stacks (Micronaut, Quarkus) it sheds its classic weakness — the spec exists before deploy, so it can be linted and diffed in CI like a hand-written one. Prefer contract-first when the spec is the product: public APIs, multi-team boundaries where the OpenAPI document is reviewed like code, or polyglot consumers generating their own clients. The delegate pattern (generated interface, hand-written implementation) is the Spring idiom that makes contract-first survivable — never edit generated sources, regenerate on every spec change.
For events, be honest about where the contract lives: if the schema registry (Avro + compatibility rules) is the enforced artifact, AsyncAPI is documentation about it — generate or hand-write it accordingly, and let messaging carry the delivery semantics.
Related
- Backend protocols in Java — the map — parent map; the protocols these documents describe.
- API documentation — the flavors and the code-first vs contract-first trade in the abstract.
- REST & HTTP APIs in Java — the endpoints these OpenAPI documents cover, per stack.
- Messaging in Java — Kafka, JMS & AMQP — the channels an AsyncAPI document describes.
- gRPC in Java — the contract-first flavor in full.
- GraphQL on the JVM — SDL, introspection and the schema-first vs code-first split in detail.
- Java application frameworks — the landscape — the stacks this note maps onto.
- API documentation in Go and in Rust — the neighbours: codegen-shaped and derive-shaped where Java is annotation-shaped.
- Application layer — primary adapters as deployment units
— where these contract artifacts live in the house layout: the
-apimodule of each primary adapter.