Stop Fighting SpringDoc: How Quarkus Makes OpenAPI Boring Again
Spring developers spend hours tuning OpenAPI configs that Quarkus generates correctly by default. This is how the zero-config model actually works.
The incident report started with a sentence nobody wanted to read on a Monday morning. A downstream team had regenerated their client from our OpenAPI spec and deployed it late Friday. By Sunday night, production traffic started failing in a way that looked like random serialization bugs, but the root cause was simpler and more embarrassing. The OpenAPI document they pulled from our Spring service was incomplete because a SpringDoc auto configuration flag had silently stopped applying after a framework upgrade.
Nothing crashed. Nothing logged an error. The spec was just wrong, and the only reason we noticed was because a partner system refused to deserialize a request that no longer matched reality.
The painful part was not fixing it. The painful part was realizing how fragile our documentation pipeline had become. A library layered on top of annotations layered on top of reflection layered on top of runtime scanning, all producing an artifact that other teams treated as a contract.
That was the week we stopped treating OpenAPI as an optional add-on and started treating it as part of the runtime itself.
Why this keeps happening in Spring-based systems
SpringDoc is popular because it feels familiar. You add a dependency, annotate your controllers, maybe add a configuration bean or two, and eventually a Swagger UI shows up. But that convenience hides a structural problem that becomes obvious at scale.
SpringDoc works by discovering your application at runtime. It reflects over controllers, resolves generics, inspects Jackson configuration, and tries to infer what your API looks like while the application is already running. Every framework upgrade, every Jackson tweak, every conditional bean can subtly change the resulting document. The OpenAPI spec is not a first-class artifact. It is a side effect.
In distributed systems, side effects make terrible contracts.
Quarkus takes a very different position. OpenAPI is not generated by scanning a running application. It is derived from build-time metadata and the same model Quarkus uses to wire your REST layer itself. That difference is why there is no Quarkus equivalent of SpringDoc. There does not need to be one.
What “zero configuration” actually means in Quarkus
Zero configuration does not mean magic. It means fewer moving parts and fewer chances to drift.
In Quarkus, OpenAPI support is part of the platform. When you use the Quarkus REST stack, the framework already knows your endpoints, your HTTP methods, your request bodies, and your response types at build time. Generating an OpenAPI document is not a separate concern. It is just another view of the same model.
There is no starter library to tune. There is no scanning phase to debug. If your endpoint exists, it is in the OpenAPI document. If it does not compile, it does not exist anywhere.
This is why Spring developers often describe the experience as boring when they first see it. Nothing to configure. Nothing to fix. Nothing to explain to the next person on call.
Bootstrapping the service
You need Java 21, Maven, and the Quarkus CLI installed.
We start the same way every production service should start, with a minimal REST API and nothing else layered on top.
quarkus create app com.themainthread:order-service \
--java=21 \
--extension=quarkus-rest-jackson,quarkus-smallrye-openapi
cd order-serviceThe extensions matter, but not for the reason Spring developers expect.
The REST extension defines the programming model. The OpenAPI extension does not scan your code later. It plugs into the same build-time pipeline that wires your endpoints. That coupling is the entire point.
The project already builds. The OpenAPI endpoint already exists. We just have not written any code yet.
A REST endpoint that becomes a contract automatically
In Spring, you would start thinking about @Operation, @ApiResponse, or customizers before you even write the endpoint. In Quarkus, you write the endpoint first and let the contract follow.
Here is a simple resource that exposes orders.
package com.themainthread;
import java.util.List;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("/orders")
@Produces(MediaType.APPLICATION_JSON)
public class OrderResource {
@GET
public List<Order> list() {
return List.of(
new Order("A-123", 42),
new Order("B-456", 7));
}
}The model is equally unremarkable.
package com.themainthread;
public record Order(String id, int quantity) {
}There are no OpenAPI annotations here. None. And that is intentional.
When Quarkus builds this application, it already knows that /orders exists, that it responds to GET, that it produces JSON, and that it returns a list of Order. The OpenAPI document is generated from that knowledge, not inferred later.
You can run the application now.
quarkus devNavigate to /q/openapi and your browser will challenge you with a complete OpenAPI document. Navigate to /q/swagger-ui and you get a working UI without a single line of configuration.
Nothing was enabled. Nothing was customized. Nothing was scanned at runtime.
Adding precision without reintroducing fragility
At some point, you want descriptions, examples, or explicit response codes. This is where Spring developers expect Quarkus to become verbose. It does not.
Quarkus uses MicroProfile OpenAPI annotations, which are small, explicit, and local to the code they describe.
Here is the same endpoint with documentation added where it belongs.
package com.themainthread;
import java.util.List;
import org.eclipse.microprofile.openapi.annotations.Operation;
import org.eclipse.microprofile.openapi.annotations.media.Content;
import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.eclipse.microprofile.openapi.annotations.responses.APIResponse;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("/orders")
@Produces(MediaType.APPLICATION_JSON)
public class OrderResource {
@GET
@Operation(summary = "List all orders", description = "Returns all known orders")
@APIResponse(responseCode = "200", description = "Successful response", content = @Content(schema = @Schema(implementation = Order.class)))
public List<Order> list() {
return List.of(
new Order("A-123", 42),
new Order("B-456", 7));
}
}There is no global configuration class. There is no conditional behavior. The annotations live exactly where the behavior lives. If the method changes, the documentation changes with it, or the code review fails.
This is what makes the system robust over time. You cannot forget to update a separate documentation layer because there is none.
Comparing this to a typical SpringDoc setup
In Spring, the equivalent system usually includes a dependency, a configuration class, possibly a grouped API definition, and often a handful of properties that influence scanning behavior. The OpenAPI output depends on which beans exist at runtime and which conditions evaluated to true.
In Quarkus, there is no scanning phase after startup. There is no ambiguity about which endpoints exist. The OpenAPI document is derived from the same build graph that produces the application binary itself.
That difference matters when you care about startup time, native compilation, and predictable behavior across environments. It also matters when your OpenAPI spec is consumed by other teams who assume it is stable.
Production hardening and native builds
The original outage happened because documentation drifted silently. Quarkus eliminates that entire class of failure by moving OpenAPI generation to build time.
This has concrete operational consequences. Startup time does not include scanning. Native images do not need reflection configuration for documentation libraries. The OpenAPI endpoint behaves the same way in dev mode, JVM mode, and native mode.
The tradeoff is intentional. You give up dynamic tricks in exchange for determinism. For an API contract, that is not a loss. It is a requirement.
Verifying the contract
You can verify the generated OpenAPI document the same way your consumers do.
curl http://localhost:8080/q/openapiResult:
---
openapi: 3.1.0
components:
schemas:
Order:
type: object
properties:
id:
type: string
quantity:
type: integer
format: int32
paths:
/orders:
get:
summary: List all orders
description: Returns all known orders
responses:
"200":
description: Successful response
content:
application/json:
schema:
$ref: "#/components/schemas/Order"
tags:
- Order Resource
info:
title: order-service API
version: 1.0.0-SNAPSHOTThe response includes the /orders path, the GET operation, the schema for Order, and the correct response metadata. There is no discrepancy between what runs and what is documented because both are produced from the same source.
Closing the loop
SpringDoc tries to make documentation easy by doing more work at runtime. Quarkus makes documentation reliable by doing the work earlier and only once.
If your OpenAPI document is a contract and not a convenience feature, boring and predictable beats clever every time.
If you want to dig deeper, make sure to read my older articles on the topic:
Unlock the Power of Your APIs: A Hands-On Introduction to Quarkus OpenAPI with Swagger
You're a skilled Java developer, crafting elegant and efficient microservices with the fast Quarkus framework. You've built a fantastic application, maybe something that processes customer orders or even analyzes sensor data. It works perfectly, and you're proud of your creation. But now comes the inevitable question: how do other developers, or even au…
Choosing Your OpenAPI Strategy with Quarkus vs. Spring
In modern application development, particularly within microservices architectures, clearly defined API contracts are crucial. The OpenAPI Specification (OAS) has become the de facto standard for describing RESTful APIs. How you generate and maintain
Brewing Better APIs: OpenAPI and Quarkus for Java Developers
APIs are promises. They connect frontends, backends, and third-party consumers across an enterprise. But a promise written only in code is fragile: documentation drifts, expectations misalign, and integrations break.
OpenAPI Meets Qwen: AI-Powered API Docs with Quarkus, LangChain4j & Ollama
This tutorial shows how to dynamically generate API documentation using Quarkus, LangChain4j, the Qwen model from Ollama, and the OpenAPI output from Quarkus. In dev mode, the docs are generated on-the-fly. It's fast, flexible, and and a potential productivity booster.







