Skip to content
Spring Boot sb web 3 min read

Swagger / OpenAPI Docs

Good APIs ship with documentation that stays in sync with the code. springdoc-openapi inspects your Spring controllers at runtime and produces an OpenAPI 3 specification plus an interactive Swagger UI, with almost zero configuration. This page covers the dependency, the generated endpoints, enriching docs with annotations, and grouping multiple APIs.

Adding springdoc-openapi

One starter wires everything up for a Spring MVC application.

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

Note: Use the springdoc-openapi 2.x line for Spring Boot 3.x. The older springfox library is unmaintained and does not support Boot 3 / jakarta.*. Pick ...-webmvc-ui for servlet apps or ...-webflux-ui for reactive apps.

What you get for free

Start the app and two endpoints appear automatically:

EndpointWhat it serves
/v3/api-docsThe OpenAPI 3 spec as JSON
/v3/api-docs.yamlThe spec as YAML
/swagger-ui.htmlInteractive Swagger UI (redirects to /swagger-ui/index.html)

Request:

curl http://localhost:8080/v3/api-docs

Output (excerpt):

{
  "openapi": "3.0.1",
  "info": { "title": "OpenAPI definition", "version": "v0" },
  "paths": {
    "/api/products/{id}": {
      "get": {
        "operationId": "findById",
        "parameters": [
          { "name": "id", "in": "path", "required": true,
            "schema": { "type": "integer", "format": "int64" } }
        ],
        "responses": { "200": { "description": "OK" } }
      }
    }
  }
}

Customizing the API info

Define a global OpenAPI bean to set title, version, contact, and servers.

import io.swagger.v3.oas.models.*;
import io.swagger.v3.oas.models.info.*;
import org.springframework.context.annotation.*;

@Configuration
public class OpenApiConfig {

    @Bean
    public OpenAPI productApi() {
        return new OpenAPI()
                .info(new Info()
                        .title("Product API")
                        .version("v1")
                        .description("Catalog and inventory endpoints")
                        .contact(new Contact().name("API Team").email("[email protected]")));
    }
}

Or set basics via properties:

springdoc.swagger-ui.path=/docs
springdoc.api-docs.path=/v3/api-docs
springdoc.packages-to-scan=com.example.api

Annotating endpoints with @Operation

@Operation and @ApiResponse document a handler’s intent and possible responses, which Swagger UI renders.

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

@RestController
@RequestMapping("/api/products")
@Tag(name = "Products", description = "Catalog operations")
public class ProductController {

    @Operation(summary = "Get a product by id",
            description = "Returns a single product or 404 if it does not exist")
    @ApiResponses({
        @ApiResponse(responseCode = "200", description = "Found"),
        @ApiResponse(responseCode = "404", description = "Not found")
    })
    @GetMapping("/{id}")
    public ProductResponse findById(@PathVariable Long id) {
        return service.findById(id);
    }
}

Describing models with @Schema

@Schema documents DTO fields — descriptions, examples, and constraints — directly on a record or class.

import io.swagger.v3.oas.annotations.media.Schema;
import java.math.BigDecimal;

public record ProductRequest(
        @Schema(description = "Display name", example = "Mechanical Keyboard")
        String name,

        @Schema(description = "Unit price in USD", example = "89.99", minimum = "0")
        BigDecimal price,

        @Schema(description = "Optional details", maxLength = 280)
        String description) {}

Bean Validation constraints (@NotBlank, @Positive, @Size) are also reflected into the schema automatically, so documented validation matches enforced validation.

Grouping APIs

Large applications split docs into named groups (e.g. public vs admin) using GroupedOpenApi beans. Each group gets its own entry in the Swagger UI dropdown.

import org.springdoc.core.models.GroupedOpenApi;
import org.springframework.context.annotation.*;

@Configuration
public class ApiGroups {

    @Bean
    public GroupedOpenApi publicApi() {
        return GroupedOpenApi.builder()
                .group("public")
                .pathsToMatch("/api/**")
                .build();
    }

    @Bean
    public GroupedOpenApi adminApi() {
        return GroupedOpenApi.builder()
                .group("admin")
                .pathsToMatch("/admin/**")
                .build();
    }
}

Documenting security

When the API is secured, declare the scheme so Swagger UI can send credentials in “Try it out.”

import io.swagger.v3.oas.annotations.security.SecurityScheme;
import io.swagger.v3.oas.annotations.enums.SecuritySchemeType;

@SecurityScheme(
        name = "bearerAuth",
        type = SecuritySchemeType.HTTP,
        scheme = "bearer",
        bearerFormat = "JWT")
@Configuration
public class OpenApiSecurityConfig { }

Tip: In production, disable or lock down Swagger UI. Set springdoc.swagger-ui.enabled=false (or restrict the path via Spring Security) so the interactive console is not publicly exposed.

Pitfalls

  • The wrong starter (webflux vs webmvc) yields a blank UI — match it to your stack.
  • springfox does not work on Spring Boot 3; migrate to springdoc-openapi.
  • Swagger UI left open in production leaks your full API surface — gate it behind auth or disable it.
Last updated June 13, 2026
Was this helpful?