Skip to content
ExponaBLOG
JAVA, SPRING FRAMEWORK & SPRING BOOT · DAY 036 / 100

OpenAPI docs with springdoc: what developers should know

Generate accurate API docs from your code.

Expona Admin7 min read

Why API documentation matters in production

In most teams we work with, API documentation falls behind—contracts drift from reality, frontend and backend engineers waste time on Slack clarifying endpoints, and onboarding new team members becomes painful. OpenAPI docs with springdoc: what developers should know is about closing that gap without manual effort. When your API documentation is generated directly from your code, it stays accurate and becomes a living contract that the whole team trusts.

Springdoc-openapi bridges Spring Boot and the OpenAPI specification, generating a machine-readable schema automatically. This schema powers interactive documentation, enables typed client generation, and supports API tooling across your organization. In this article, we'll walk through a practical setup that we've used on production systems handling thousands of requests daily.

Add springdoc-openapi-starter-webmvc-ui

The first step is adding the dependency to your Spring Boot project. If you're using Maven, add this to your pom.xml:

java
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
    <version>2.3.0</version>
</dependency>

For Gradle:

java
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0'

That's it. Springdoc scans your Spring Boot application for @RestController and @RequestMapping annotations automatically. It reads method signatures, parameter types, and response types, then generates an OpenAPI 3.0 schema without any additional configuration. If you're running Spring Boot 3.x, this starter is what you want—it replaces the older springfox setup that many of us used years ago.

The library runs at startup with minimal performance impact. In development, it's fast enough that we don't notice it. In production, the schema generation happens once on startup and is then cached, so your API endpoints don't carry any documentation overhead per request.

Swagger UI at /swagger-ui.html

Once you add the dependency and restart your application, navigate to http://localhost:8080/swagger-ui.html and you'll see an interactive interface showing all your endpoints. This is Swagger UI, powered by the OpenAPI schema that springdoc generated.

Let's say we have a simple e-commerce API with a product controller:

java
@RestController
@RequestMapping("/api/products")
public class ProductController {

    private final ProductService productService;

    public ProductController(ProductService productService) {
        this.productService = productService;
    }

    @GetMapping("/{id}")
    public ResponseEntity<ProductDTO> getProduct(@PathVariable Long id) {
        return ResponseEntity.ok(productService.findById(id));
    }

    @PostMapping
    public ResponseEntity<ProductDTO> createProduct(@RequestBody CreateProductRequest request) {
        ProductDTO created = productService.create(request);
        return ResponseEntity.status(HttpStatus.CREATED).body(created);
    }
}

Without any additional annotations, Swagger UI will display both endpoints with:

  • Request and response schemas (inferred from DTO classes)
  • Path and request body parameters
  • HTTP status codes (GET returns 200, POST returns 201)
  • A "Try it out" button to test endpoints directly from the UI

This alone is valuable. Frontend developers see the exact shape of responses. QA engineers can test endpoints. New team members can explore the API without reading source code.

You can customize Swagger UI behavior in your application.properties or application.yml:

java
springdoc.swagger-ui.path=/api/docs
springdoc.swagger-ui.enabled=true
springdoc.api-docs.path=/api/openapi

In production, many teams disable the UI itself (to avoid exposing documentation to the internet) but keep the machine-readable schema available:

java
springdoc.swagger-ui.enabled=${SWAGGER_UI_ENABLED:false}
springdoc.api-docs.enabled=true

The JSON schema is still generated at /v3/api-docs, which downstream tools can consume even if the interactive UI is hidden.

Annotate with @Operation only where defaults are unclear

Here's a key principle we follow: if the default documentation generated by springdoc is clear, don't add annotations. Annotations clutter your code and create maintenance burden. Only annotate when the generated docs would confuse someone.

Consider a controller method with business logic that isn't obvious from the method name:

java
@PostMapping("/{id}/activate-with-verification")
public ResponseEntity<ProductDTO> activateWithVerification(
    @PathVariable Long id,
    @RequestBody VerificationCodeRequest request
) {
    ProductDTO updated = productService.activateAndVerify(id, request.getCode());
    return ResponseEntity.ok(updated);
}

The method name tells the story, so the generated documentation is likely sufficient. But if you had a method like:

java
@PostMapping("/{id}/execute")
public ResponseEntity<?> execute(@PathVariable Long id, @RequestBody Object payload) {
    // Some complex business logic
}

Here, you should add @Operation and @io.swagger.v3.oas.annotations.media.Content to clarify what's happening:

java
@PostMapping("/{id}/execute")
@Operation(
    summary = "Trigger bulk price recalculation",
    description = "Initiates an asynchronous job to recalculate prices for all products in this batch. " +
                  "Returns immediately with a job ID. Poll /jobs/{jobId} to track progress.",
    tags = {"products"}
)
@ApiResponse(
    responseCode = "202",
    description = "Job accepted",
    content = @Content(schema = @Schema(implementation = JobResponse.class))
)
@ApiResponse(responseCode = "400", description = "Invalid batch ID")
public ResponseEntity<JobResponse> execute(@PathVariable Long id, @RequestBody Object payload) {
    String jobId = productService.scheduleRecalculation(id);
    return ResponseEntity.status(HttpStatus.ACCEPTED)
        .body(new JobResponse(jobId));
}

This annotation clarifies the asynchronous nature and the expected 202 response code—information that wouldn't be obvious from the code alone.

Use @Operation for method-level documentation, @Parameter for query or path parameters when their purpose is unclear, and @ApiResponse for non-standard status codes or error scenarios. Keep it minimal. We've seen projects where every method has a wall of annotation text; that's a sign the code itself isn't clear enough.

Generate typed clients for your frontend

The real power of standardized OpenAPI documentation appears when you use it to generate client code. Instead of frontend developers writing fetch calls by hand or copying types from documentation, you generate a typed HTTP client automatically from your OpenAPI schema.

Tools like OpenAPI Generator, Swagger Codegen, and others can generate clients in TypeScript, Python, Go, or any language. For a Next.js or React frontend, you might use openapi-typescript:

java
npx openapi-typescript http://localhost:8080/v3/api-docs -o ./src/api/types.ts

This generates a TypeScript file with all your API types and endpoints:

java
export interface ProductDTO {
  id: number;
  name: string;
  price: number;
  createdAt: string;
}

export interface CreateProductRequest {
  name: string;
  price: number;
}

Then your frontend can use these types with a library like openapi-fetch:

java
import createClient from "openapi-fetch";
import type { paths } from "./api/types";

const client = createClient<paths>({
  baseUrl: "http://localhost:8080",
});

const { data, error } = await client.GET("/api/products/{id}", {
  params: { path: { id: 42 } },
});

if (data) {
  console.log(data.name); // TypeScript knows this is a string
}

No manual fetch calls. No type mismatches between frontend and backend. When you change the API schema and regenerate the client, TypeScript will immediately flag any code that's now incompatible.

Set up client generation as part of your build pipeline. In a monorepo, you might run code generation when the backend schema changes. In separate repos, your frontend can pull the latest schema on each build or on demand.

Common mistakes

  • Over-annotating: Adding @Operation, @Parameter, and @ApiResponse to every method. The generated docs are usually good enough without them. Annotate only where the defaults mislead.
  • Exposing Swagger UI in production: Leaving /swagger-ui.html accessible to the internet exposes your API surface to casual attackers. Disable the UI with springdoc.swagger-ui.enabled=false in production, but keep the schema available for internal tooling.
  • Using generic response types: Returning ResponseEntity<?> or ResponseEntity<Object> prevents springdoc from inferring the actual response schema. Use concrete DTOs so the generated schema is accurate.
  • Forgetting to version the schema: The default OpenAPI path is /v3/api-docs. If you have multiple versions of your API, set up separate schema endpoints so clients can generate against the version they need.
  • Not validating generated schemas: After generating a client, test it. Make sure the types actually match your API responses. A mismatch usually indicates a DTO that doesn't align with your actual response structure.

Key takeaways

  • Add springdoc-openapi-starter-webmvc-ui to get automatic OpenAPI schema generation with zero configuration.
  • Swagger UI at /swagger-ui.html provides interactive documentation out of the box—useful in development and useful for onboarding.
  • Use @Operation and related annotations only when the generated documentation would be unclear. Prefer readable code over annotation overhead.
  • Generate typed clients for your frontend from the OpenAPI schema to eliminate manual type definitions and catch API changes at build time.
  • Disable Swagger UI in production but keep the machine-readable schema available for internal tooling and client generation.
  • Keep your DTOs aligned with your actual responses; springdoc's schema generation is only accurate when your code is clean.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles