API Docs with OpenAPI & springdoc

Reviewed & published by Brayan K

Add springdoc-openapi and get an interactive Swagger UI at /swagger-ui.html plus a machine-readable spec at /v3/api-docs — then enrich it with a few annotations.

Part of the free Java course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

Before You Start

You should have built REST controllers and DTOs and applied your database migrations with Flyway. Comfort with @RestController and @GetMapping is assumed.

What You'll Learn

The Big Idea — A Catalogue That Writes Itself

💡 Analogy: A good shop has a catalogue listing every product with a photo, description and price. OpenAPI is that catalogue for your API, and springdoc is a clerk who walks the shelves (your controllers) and writes the catalogue for you. Annotations like @Operation and @Schema are the captions you add so each entry reads well.

You write the endpoints; springdoc writes — and keeps current — the documentation.

1️⃣ Add the Dependency, Get Docs for Free

Add the springdoc starter and restart. You immediately get /swagger-ui.html and /v3/api-docs — no extra code required.

<!-- pom.xml — the ONLY thing needed for a working Swagger UI. -->
<!-- After adding this, start the app and open:                   -->
<!--   /swagger-ui.html   (interactive UI)                        -->
<!--   /v3/api-docs        (raw OpenAPI 3 JSON)                    -->

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

2️⃣ Enrich Endpoints with @Operation, @ApiResponse, @Tag

springdoc infers paths and parameters automatically; these annotations add summaries, response details and grouping so the docs read clearly.

// (Conceptual — needs Spring Web + springdoc on the classpath.)
package com.example.demo;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/users")
@Tag(name = "Users", description = "Create and read application users")  // groups endpoints
public class UserController {

    @Operation(summary = "Get a user by id",
               description = "Returns the user with the given id, or 404 if none exists.")
    @ApiResponses({
        @ApiResponse(responseCode = "200", description = "User found"),
        @ApiResponse(responseCode = "404", description = "No user with that id")
    })
    @GetMapping("/{id}")
    public ResponseEntity<UserDto> getUser(@PathVariable long id) {
        // ... look up and return the user ...
        return ResponseEntity.ok(new UserDto(id, "ada@example.com", "Ada"));
    }
}

3️⃣ Documenting DTOs with @Schema

Add @Schema to a model and its fields to give each property a description and an example — both appear in Swagger UI and in the JSON spec.

package com.example.demo;

import io.swagger.v3.oas.annotations.media.Schema;

// @Schema enriches how this DTO appears in the generated docs:
// each field gets a description and an example value in Swagger UI.
public record UserDto(

    @Schema(description = "Unique user id", example = "42")
    long id,

    @Schema(description = "Email address", example = "ada@example.com")
    String email,

    @Schema(description = "Display name", example = "Ada")
    String displayName
) {}

4️⃣ The Machine-Readable Spec: /v3/api-docs

Swagger UI is just a viewer over this JSON. Tools consume /v3/api-docs directly to generate clients, mocks and contract tests.

// A trimmed view of what GET /v3/api-docs returns — the
// machine-readable OpenAPI 3 description tools consume:
{
  "openapi": "3.0.1",
  "paths": {
    "/api/users/{id}": {
      "get": {
        "tags": ["Users"],
        "summary": "Get a user by id",
        "responses": {
          "200": { "description": "User found" },
          "404": { "description": "No user with that id" }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "UserDto": {
        "type": "object",
        "properties": {
          "id":          { "type": "integer", "example": 42 },
          "email":       { "type": "string",  "example": "ada@example.com" },
          "displayName": { "type": "string",  "example": "Ada" }
        }
      }
    }
  }
}

5️⃣ The Idea, Concretely

To make the spec-from-metadata idea tangible, here is a plain-Java stand-in that turns endpoint metadata into a machine-readable description — with real output.

// 🎯 YOUR TURN — replace each ___ using the hint beside it.
import java.util.*;

public class Main {
    record Param(String name, String in, boolean required, String type) {}
    record Operation(String method, String path, String summary, List<Param> params, Map<Integer, String> responses) {}

    static String indent(int n) { return "  ".repeat(n); }

    static void render(List<Operation> ops) {
        Map<String, List<Operation>> byPath = new TreeMap<>();
        // 1) Several operations can share one path (GET and POST /books),
        //    so the document groups by path first, then by method.
        for (Operation op : ops) byPath.___(op.path(), k -> new ArrayList<>()).add(op);   // 👉 replace ___ with computeIfAbsent

        System.out.println("openapi: 3.1.0");
        System.out.println("paths:");
        for (var entry : byPath.entrySet()) {
            System.out.println(indent(1) + entry.getKey() + ":");
            for (Operation op : entry.getValue()) {
                // 2) OpenAPI wants the verb in lower case.
                System.out.println(indent(2) + op.method().___() + ":");   // 👉 replace ___ with toLowerCase
                System.out.println(indent(3) + "summary: " + op.summary());
                // 3) Leave the key out entirely when there are none.
                if (!op.params().___()) {                  // 👉 replace ___ with isEmpty
                    System.out.println(indent(3) + "parameters:");
                    for (Param p : op.params()) {
                        System.out.println(indent(4) + "- name: " + p.name());
                        System.out.println(indent(5) + "in: " + p.in());
                        // 4) A path variable is always required; a query one
                        //    need not be, and this is where you say which.
                        System.out.println(indent(5) + "required: " + p.___());   // 👉 replace ___ with required
                        System.out.println(indent(5) + "schema: { type: " + p.type() + " }");
                    }
                }
                System.out.println(indent(3) + "responses:");
                // 5) A TreeMap so the status codes come out in order every
                //    time — a HashMap would shuffle them between runs.
                for (var r : new ___<>(op.responses()).entrySet()) {   // 👉 replace ___ with TreeMap
                    System.out.println(indent(4) + "'" + r.getKey() + "': " + r.getValue());
                }
            }
        }
    }

    public static void main(String[] args) {
        render(List.of(
            new Operation("GET", "/books", "List every book", List.of(), Map.of(200, "a list of books")),
            new Operation("GET", "/books/{id}", "Fetch one book", List.of(new Param("id", "path", true, "integer")), Map.of(200, "the book", 404, "no such book"))
        ));
    }
}

🧠 Quick Recall

Predict before you peek.

Answer: at /v3/api-docs — the machine-readable description tools consume.

Answer: @Schema, with a description and example.

Common Errors

Pro Tips

📋 Quick Reference

ItemValue / annotationNote
UI/swagger-ui.htmlinteractive docs
spec/v3/api-docsOpenAPI 3 JSON
endpoint@Operationsummary/description
outcome@ApiResponsea status + meaning
group@Tagcluster endpoints
model@Schemafield docs/example

📚 Resources / Keep Learning

🎉 Lesson Complete!

You now know how springdoc-openapi auto-generates an OpenAPI 3 spec and Swagger UI, where to find them (/swagger-ui.html and /v3/api-docs), how to enrich docs with @Operation, @ApiResponse, @Tag and @Schema, and why a machine-readable description is so valuable.

Practice quiz

What does springdoc-openapi provide for a Spring Boot app?

  • Auto-generated OpenAPI docs and a Swagger UI
  • A logging backend
  • A build tool
  • A database driver

Answer: Auto-generated OpenAPI docs and a Swagger UI. springdoc-openapi inspects your controllers at runtime and produces an OpenAPI 3 description plus an interactive Swagger UI, with almost no configuration.

What is the OpenAPI specification?

  • A database migration tool
  • A unit-testing library
  • A standard, machine-readable format for describing REST APIs
  • A Java web framework

Answer: A standard, machine-readable format for describing REST APIs. OpenAPI (formerly Swagger) is a language-agnostic, machine-readable standard for describing the endpoints, parameters, schemas and responses of a REST API.

At what URL is the Swagger UI served by default with springdoc?

  • /docs
  • /swagger-ui.html
  • /openapi
  • /api/swagger

Answer: /swagger-ui.html. springdoc serves the interactive Swagger UI at /swagger-ui.html by default, which redirects to the bundled UI page.

Where does springdoc expose the raw OpenAPI JSON description?

  • /swagger.json
  • /api/openapi.yaml
  • /docs/json
  • /v3/api-docs

Answer: /v3/api-docs. The machine-readable OpenAPI JSON is served at /v3/api-docs by default; a YAML variant is also available.

Which annotation describes a single endpoint operation (summary, description)?

  • @Operation
  • @Bean
  • @Schema
  • @Tag

Answer: @Operation. @Operation documents one endpoint method, letting you set its summary and a longer description shown in Swagger UI.

Which annotation documents a specific HTTP status outcome of an endpoint?

  • @Transactional
  • @ApiResponse
  • @RequestMapping
  • @Component

Answer: @ApiResponse. @ApiResponse describes one possible response, typically a status code such as 200 or 404 plus its meaning and body schema.

Which annotation groups related endpoints under a heading in Swagger UI?

  • @Section
  • @Category
  • @Group
  • @Tag

Answer: @Tag. @Tag attaches a named group (often at the controller level) so related operations appear together in the UI.

Which annotation documents a field of a DTO/model?

  • @Valid
  • @Column
  • @Schema
  • @JsonProperty

Answer: @Schema. @Schema (from io.swagger.v3.oas.annotations.media) documents a model or one of its fields with a description, example and constraints.

Why does a machine-readable API description matter?

  • It encrypts traffic
  • It lets tools generate clients, mock servers and tests automatically
  • It replaces the database
  • It makes the JVM faster

Answer: It lets tools generate clients, mock servers and tests automatically. Because the OpenAPI document is structured data, tooling can generate typed client SDKs, mock servers, contract tests and live docs without anyone hand-writing them.

How much code must you usually write to get a basic Swagger UI with springdoc?

  • Essentially none beyond adding the dependency
  • A YAML file for every endpoint
  • A separate documentation server
  • A full custom controller

Answer: Essentially none beyond adding the dependency. Adding the springdoc starter dependency is enough to get a working /swagger-ui.html and /v3/api-docs; the annotations only enrich the auto-generated docs.

Continue this course

Frequently asked questions

What is OpenAPI and how does springdoc-openapi relate to it?

OpenAPI (formerly Swagger) is a language-agnostic, machine-readable standard for describing a REST API: its endpoints, HTTP methods, parameters, request and response bodies, status codes and data schemas, all expressed as structured JSON or YAML. springdoc-openapi is the library that produces an OpenAPI 3 document for a Spring Boot application automatically. It scans your controllers and models at runtime, infers the paths, parameters and schemas from your annotations and types, and exposes both the raw description and an interactive UI. So OpenAPI is the format; springdoc is the tool that generates that format from your live Spring code without you hand-writing a specification file.

Where do I find the docs once springdoc is added?

Adding the springdoc starter dependency is enough to get working documentation. Two endpoints appear: /swagger-ui.html serves an interactive Swagger UI where you can browse every endpoint, read its summary, see request and response schemas, and even send live requests to try it out; and /v3/api-docs returns the raw OpenAPI 3 description as JSON (a YAML variant is also available). The UI is generated from that JSON. You usually do not configure anything to get this baseline; the annotations covered in this lesson only enrich what is shown, adding human-friendly summaries, descriptions, examples and response details on top of what springdoc already infers.

What do @Operation, @ApiResponse, @Tag and @Schema each do?

These annotations layer documentation onto the auto-generated description. @Operation documents a single endpoint method, letting you set a short summary and a longer description that appear in Swagger UI. @ApiResponse (usually several, grouped in @ApiResponses) documents the possible outcomes of an endpoint, such as 200 for success and 404 for not found, each with a description and optional body schema. @Tag groups related endpoints under a named heading, typically applied at the controller level so all its operations cluster together. @Schema documents a model or one of its fields, supplying a description, an example value and constraints. Together they turn a bare auto-generated spec into clear, example-rich documentation.

How do I document the DTOs my API sends and receives?

springdoc already infers a schema for each DTO from its type, but you make it far more useful with @Schema on the class and its fields. On a field you can add a description explaining what it means, an example showing a realistic value, and constraints such as required, minimum or maximum. These appear in Swagger UI and in the components/schemas section of /v3/api-docs, so consumers know exactly what each property is for and what a valid value looks like. Because the schema is part of the machine-readable document, tools that generate client code or validation can use those constraints directly, keeping the contract and the documentation in one place.

Why does a machine-readable API description matter in practice?

Because the OpenAPI document is structured data rather than prose, an entire ecosystem of tooling can consume it automatically. You can generate typed client SDKs in many languages, stand up mock servers that respond according to the spec, write contract tests that verify the real API matches its description, import the API into tools like Postman, and publish always-up-to-date interactive docs. Since springdoc derives the document from your live code, it stays in sync as the API evolves, so the documentation never drifts from reality the way a hand-maintained document would. This shared, accurate contract is what lets front-end and back-end teams, and external consumers, work against the same agreed interface.