Spring Boot: Integrating Swagger / OpenAPI With springdoc-openapi

Abbreviations

Abbreviation Full name Meaning here
API Application Programming Interface The HTTP endpoints your controllers expose
OAS OpenAPI Specification The vendor-neutral standard for describing HTTP APIs
REST Representational State Transfer The resource-oriented HTTP style most Spring controllers follow
HTTP Hypertext Transfer Protocol The protocol the API and the documentation are served over
JSON / YAML JavaScript Object Notation / YAML Ain't Markup Language The two formats an OpenAPI document is written in
UI User Interface Swagger UI, the browser page that renders the document
MVC Model--View--Controller Spring Web MVC, the servlet-based web stack
DTO Data Transfer Object Request/response classes such as CreateBookRequest
JWT JSON Web Token The bearer token format declared in the security scheme
CI Continuous Integration The build pipeline that can export and diff the spec
ISBN International Standard Book Number Field used in the sample model

1. The short answer

Add one dependency --- springdoc-openapi-starter-webmvc-ui --- and a Spring Boot application gets:

  • /v3/api-docs --- an OpenAPI 3.1 JSON document generated from your code (controllers, DTOs, validation annotations) at runtime;
  • /v3/api-docs.yaml --- the same document as YAML;
  • /swagger-ui.html --- Swagger UI, an interactive page that renders the document and lets you send real requests.

Everything after that --- titles, descriptions, examples, error responses, security schemes, groups --- is optional polish added with annotations, one OpenAPI bean, and springdoc.* properties.

Everything in this article was built and run against Spring Boot 4.1.1 + springdoc-openapi 3.1.1 on Java 17; the JSON excerpts are real output.

2. Swagger, OpenAPI, springfox, springdoc --- sorting out the names

Name What it is
Swagger The original API-description format (Swagger 2.0) and today a family of SmartBear tools: Swagger UI, Swagger Editor, Swagger Codegen.
OpenAPI Swagger 2.0 donated to the Linux Foundation and renamed in 2016. OAS 3.0 (2017) and 3.1 (2021) are the current versions. The format is OpenAPI; the tools are still called Swagger.
Swagger UI The HTML/JavaScript page that turns an OpenAPI document into browsable, clickable documentation.
springfox The old Spring integration (@EnableSwagger2, Docket). Last release 3.0.0 in 2020; it does not work with Spring Boot 3+ (Jakarta EE namespace). Do not use it for new code.
springdoc-openapi The maintained Spring integration. Reads Spring's own metadata, emits OpenAPI 3, bundles Swagger UI. This is what this article uses.

3. How springdoc works

#mermaid-svg-JS50rEKJ3mgvUZId{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-JS50rEKJ3mgvUZId .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-JS50rEKJ3mgvUZId .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-JS50rEKJ3mgvUZId .error-icon{fill:#552222;}#mermaid-svg-JS50rEKJ3mgvUZId .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-JS50rEKJ3mgvUZId .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-JS50rEKJ3mgvUZId .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-JS50rEKJ3mgvUZId .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-JS50rEKJ3mgvUZId .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-JS50rEKJ3mgvUZId .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-JS50rEKJ3mgvUZId .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-JS50rEKJ3mgvUZId .marker{fill:#333333;stroke:#333333;}#mermaid-svg-JS50rEKJ3mgvUZId .marker.cross{stroke:#333333;}#mermaid-svg-JS50rEKJ3mgvUZId svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-JS50rEKJ3mgvUZId p{margin:0;}#mermaid-svg-JS50rEKJ3mgvUZId .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-JS50rEKJ3mgvUZId .cluster-label text{fill:#333;}#mermaid-svg-JS50rEKJ3mgvUZId .cluster-label span{color:#333;}#mermaid-svg-JS50rEKJ3mgvUZId .cluster-label span p{background-color:transparent;}#mermaid-svg-JS50rEKJ3mgvUZId .label text,#mermaid-svg-JS50rEKJ3mgvUZId span{fill:#333;color:#333;}#mermaid-svg-JS50rEKJ3mgvUZId .node rect,#mermaid-svg-JS50rEKJ3mgvUZId .node circle,#mermaid-svg-JS50rEKJ3mgvUZId .node ellipse,#mermaid-svg-JS50rEKJ3mgvUZId .node polygon,#mermaid-svg-JS50rEKJ3mgvUZId .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-JS50rEKJ3mgvUZId .rough-node .label text,#mermaid-svg-JS50rEKJ3mgvUZId .node .label text,#mermaid-svg-JS50rEKJ3mgvUZId .image-shape .label,#mermaid-svg-JS50rEKJ3mgvUZId .icon-shape .label{text-anchor:middle;}#mermaid-svg-JS50rEKJ3mgvUZId .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-JS50rEKJ3mgvUZId .rough-node .label,#mermaid-svg-JS50rEKJ3mgvUZId .node .label,#mermaid-svg-JS50rEKJ3mgvUZId .image-shape .label,#mermaid-svg-JS50rEKJ3mgvUZId .icon-shape .label{text-align:center;}#mermaid-svg-JS50rEKJ3mgvUZId .node.clickable{cursor:pointer;}#mermaid-svg-JS50rEKJ3mgvUZId .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-JS50rEKJ3mgvUZId .arrowheadPath{fill:#333333;}#mermaid-svg-JS50rEKJ3mgvUZId .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-JS50rEKJ3mgvUZId .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-JS50rEKJ3mgvUZId .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-JS50rEKJ3mgvUZId .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-JS50rEKJ3mgvUZId .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-JS50rEKJ3mgvUZId .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-JS50rEKJ3mgvUZId .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-JS50rEKJ3mgvUZId .cluster text{fill:#333;}#mermaid-svg-JS50rEKJ3mgvUZId .cluster span{color:#333;}#mermaid-svg-JS50rEKJ3mgvUZId div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-JS50rEKJ3mgvUZId .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-JS50rEKJ3mgvUZId rect.text{fill:none;stroke-width:0;}#mermaid-svg-JS50rEKJ3mgvUZId .icon-shape,#mermaid-svg-JS50rEKJ3mgvUZId .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-JS50rEKJ3mgvUZId .icon-shape p,#mermaid-svg-JS50rEKJ3mgvUZId .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-JS50rEKJ3mgvUZId .icon-shape .label rect,#mermaid-svg-JS50rEKJ3mgvUZId .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-JS50rEKJ3mgvUZId .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-JS50rEKJ3mgvUZId .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-JS50rEKJ3mgvUZId :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Spring Boot application
fetches
@RestController

mappings
DTOs + Bean Validation

@NotBlank, @Size...
swagger annotations

@Operation, @Schema
OpenAPI bean

  • springdoc.* properties
    springdoc

scanner
/v3/api-docs

(JSON / YAML)
/swagger-ui/index.html

(static assets)
Browser
Client generators,

Postman, gateways

This diagram answers: where does the OpenAPI document come from, and who consumes it?

springdoc asks Spring MVC for its RequestMappingHandlerMapping --- the same table Spring uses to route requests --- so paths, HTTP methods, parameters and return types come straight from your real mappings. It then inspects DTOs with the swagger-core model resolver (Jackson-aware) and folds in validation and documentation annotations. The document is built lazily on the first request and cached.

4. Step 1 --- add the dependency

Choose the artifact that matches your web stack and Spring Boot line:

Spring Boot springdoc line Web MVC artifact WebFlux artifact
4.1.x 3.1.x springdoc-openapi-starter-webmvc-ui springdoc-openapi-starter-webflux-ui
4.0.x 3.0.x same same
3.x 2.8.x same same
2.x (legacy) 1.8.0 (final) springdoc-openapi-ui springdoc-openapi-webflux-ui

Versions checked on Maven Central on 2026-10-22. Each springdoc line is built against one Spring Boot generation --- mixing them (for example springdoc 2.x on Boot 4) fails at startup.

The -ui starters include Swagger UI; the -api variants (springdoc-openapi-starter-webmvc-api) produce only /v3/api-docs, for services where the UI is hosted elsewhere.

xml 复制代码
<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>4.1.1</version>
</parent>

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webmvc</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
    <version>3.1.1</version>
  </dependency>
</dependencies>

(Spring Boot 4 renamed spring-boot-starter-web to spring-boot-starter-webmvc; the old name still works as a deprecated alias. On Boot 3, keep spring-boot-starter-web.)

Gradle:

kotlin 复制代码
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:3.1.1")

Start the application. With zero further configuration, these work:

URL Returns
/v3/api-docs OpenAPI document (JSON)
/v3/api-docs.yaml OpenAPI document (YAML)
/v3/api-docs/{group} One group's document (see §8)
/swagger-ui.html 302 redirect to /swagger-ui/index.html
/v3/api-docs/swagger-config Settings that Swagger UI loads on start-up

5. Step 2 --- describe the API as a whole

One OpenAPI bean sets document-level metadata: title, version, description, license, and security schemes.

java 复制代码
@Configuration
public class OpenApiConfig {

    @Bean
    OpenAPI booksOpenApi() {
        return new OpenAPI()
                .info(new Info()
                        .title("Books API")
                        .version("v1")
                        .description("Catalogue service for the book store")
                        .license(new License().name("Apache 2.0")))
                .components(new Components().addSecuritySchemes("bearerAuth",
                        new SecurityScheme()
                                .type(SecurityScheme.Type.HTTP)
                                .scheme("bearer")
                                .bearerFormat("JWT")))
                .addSecurityItem(new SecurityRequirement().addList("bearerAuth"));
    }
}

The security scheme puts an Authorize button in Swagger UI; a token pasted there is sent as Authorization: Bearer ... on every "Try it out" request. addSecurityItem applies it to all operations; to apply it to selected endpoints only, drop that line and annotate those methods with @SecurityRequirement(name = "bearerAuth").

The same metadata can also be declared with @OpenAPIDefinition and @SecurityScheme annotations on any @Configuration class; the bean form is easier when values come from configuration.

6. Step 3 --- document controllers and models

springdoc already knows the paths and types. Annotations add the human parts: summaries, descriptions, examples and non-200 responses.

java 复制代码
@RestController
@RequestMapping("/api/books")
@Tag(name = "Books", description = "Manage the book catalogue")
public class BookController {

    @GetMapping("/{id}")
    @Operation(summary = "Get a book by id")
    @ApiResponses({
            @ApiResponse(responseCode = "200", description = "Book found"),
            @ApiResponse(responseCode = "404", description = "No book with that id",
                    content = @Content(schema = @Schema(implementation = ProblemDetail.class)))
    })
    public ResponseEntity<Book> get(@Parameter(description = "Book id", example = "42")
                                    @PathVariable Long id) { ... }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    @Operation(summary = "Create a book")
    @ApiResponse(responseCode = "201", description = "Book created")
    @ApiResponse(responseCode = "400", description = "Validation failed",
            content = @Content(schema = @Schema(implementation = ProblemDetail.class)))
    public Book create(@Valid @RequestBody CreateBookRequest request) { ... }
}
java 复制代码
@Schema(description = "A book in the catalogue")
public record Book(
        @Schema(description = "Server-assigned id", example = "42",
                accessMode = Schema.AccessMode.READ_ONLY)
        Long id,
        @Schema(description = "Book title", example = "Effective Java")
        String title,
        @Schema(description = "13-digit ISBN", example = "9780134685991")
        String isbn) {
}

@Schema(description = "Payload for creating a book")
public record CreateBookRequest(
        @NotBlank @Size(max = 200) @Schema(example = "Effective Java")
        String title,
        @NotBlank @Pattern(regexp = "\\d{13}") @Schema(example = "9780134685991")
        String isbn) {
}

The annotations come from io.swagger.v3.oas.annotations.* (swagger-core), pulled in by the starter.

Annotation Placed on Adds
@Tag Controller Groups operations under a heading in Swagger UI
@Operation Handler method summary, description, operationId, hidden, deprecated
@Parameter Method parameter Description, example, required for path/query/header parameters
@ApiResponse(s) Handler method Additional status codes and their body schemas
@Schema DTO class or field Description, example, accessMode, allowableValues, implementation
@Hidden Controller or method Leaves it out of the document

Bean Validation flows into the schema

This is the real generated schema for CreateBookRequest:

json 复制代码
"CreateBookRequest": {
  "type": "object",
  "description": "Payload for creating a book",
  "properties": {
    "title": { "type": "string", "example": "Effective Java", "maxLength": 200, "minLength": 0 },
    "isbn":  { "type": "string", "example": "9780134685991", "minLength": 1, "pattern": "\\d{13}" }
  },
  "required": ["isbn", "title"]
}
  • @NotBlank / @NotNull → the field is listed in required;
  • @Size(max = 200) → maxLength: 200; @Pattern → pattern; @Min/@Max → minimum/maximum.

Watch the title field: @NotBlank alone produces minLength: 1 (as on isbn), but @Size(max = 200) contributes its default min = 0, which wins. If the minimum matters to clients, write @Size(min = 1, max = 200).

Likewise, accessMode = READ_ONLY becomes "readOnly": true on Book.id, telling client generators not to send it on create.

7. Step 4 --- document errors honestly

A spec that only shows the happy path misleads its clients. Spring Framework's ProblemDetail (RFC 9457 "Problem Details for HTTP APIs") gives every error the same shape, and the @ApiResponse(... implementation = ProblemDetail.class) lines above advertise it.

Make sure the runtime matches the documentation --- turn on Spring Boot's built-in problem-details handling:

yaml 复制代码
spring:
  mvc:
    problemdetails:
      enabled: true

With it, a failed validation returns (verified):

json 复制代码
{"detail":"Invalid request content.","instance":"/api/books","status":400,"title":"Bad Request"}

Without it, Boot's default error body (timestamp, status, error, path) is returned, which does not match a ProblemDetail schema. A @RestControllerAdvice that extends ResponseEntityExceptionHandler achieves the same and lets you add field-level errors.

8. Step 5 --- group large APIs

One service often exposes public endpoints, admin endpoints and internal callbacks. GroupedOpenApi splits them into separate documents, chosen from a drop-down in Swagger UI:

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

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

Each group is served at /v3/api-docs/{group}. Groups can also filter by packagesToScan, pathsToExclude, or a custom OpenApiCustomizer. Once any group bean exists, Swagger UI offers only the groups in its drop-down; /v3/api-docs itself still serves the full, ungrouped document.

9. Useful springdoc.* properties

yaml 复制代码
springdoc:
  api-docs:
    path: /v3/api-docs          # where the JSON is served
  swagger-ui:
    path: /swagger-ui.html      # entry URL of the UI
    operations-sorter: method   # sort operations by HTTP method
    tags-sorter: alpha          # sort tags alphabetically
    try-it-out-enabled: true    # open the "Try it out" panels by default
  packages-to-scan: com.example.books
  default-produces-media-type: application/json
Property Effect
springdoc.api-docs.enabled false removes /v3/api-docs (returns 404)
springdoc.swagger-ui.enabled false removes Swagger UI
springdoc.packages-to-scan / paths-to-match Limit what the default document includes
springdoc.show-actuator Include Actuator endpoints in the document
springdoc.api-docs.version openapi_3_1 (default in springdoc 2.8+/3.x) or openapi_3_0 for tools that cannot read 3.1 yet

10. Spring Security

If Spring Security is on the classpath, the documentation endpoints are locked like everything else. Open them explicitly (in non-production profiles):

java 复制代码
@Bean
SecurityFilterChain api(HttpSecurity http) throws Exception {
    http.authorizeHttpRequests(auth -> auth
            .requestMatchers("/v3/api-docs/**", "/swagger-ui/**", "/swagger-ui.html").permitAll()
            .anyRequest().authenticated());
    // ... JWT resource server, etc.
    return http.build();
}

All three patterns are needed: the HTML entry point, the static assets under /swagger-ui/, and the JSON the page fetches (including /v3/api-docs/swagger-config).

11. Production concerns

  • Decide whether the docs should exist in production. For an internal service, a full catalogue of endpoints and models is free reconnaissance for an attacker. Common choice: enable in dev/test, disable in prod:

    yaml 复制代码
    # application-prod.yml
    springdoc:
      api-docs:
        enabled: false
      swagger-ui:
        enabled: false

    For a public API, keep the spec but publish it deliberately (developer portal, gateway), not by accident.

  • Behind a reverse proxy or load balancer , the generated servers entry ("url": "http://localhost:18082", "description": "Generated server url" in the test run) is derived from the incoming request, so it may show the internal host or http. Set server.forward-headers-strategy=framework so X-Forwarded-* headers are honoured, or declare servers explicitly in the OpenAPI bean.

  • Start-up cost is small --- the document is built on first request --- but very large APIs can take noticeable time on that first call; groups help.

12. Code-first, design-first, and the spec as a build artefact

springdoc is code-first : the code is the source of truth and the spec is derived. The alternative is design-first : write openapi.yaml by hand, review it, and generate Spring interfaces from it with openapi-generator (spring generator). Either way, treat the spec as a versioned artefact:
#mermaid-svg-0NXm9hPJtzxjvR2X{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-0NXm9hPJtzxjvR2X .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-0NXm9hPJtzxjvR2X .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-0NXm9hPJtzxjvR2X .error-icon{fill:#552222;}#mermaid-svg-0NXm9hPJtzxjvR2X .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-0NXm9hPJtzxjvR2X .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-0NXm9hPJtzxjvR2X .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-0NXm9hPJtzxjvR2X .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-0NXm9hPJtzxjvR2X .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-0NXm9hPJtzxjvR2X .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-0NXm9hPJtzxjvR2X .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-0NXm9hPJtzxjvR2X .marker{fill:#333333;stroke:#333333;}#mermaid-svg-0NXm9hPJtzxjvR2X .marker.cross{stroke:#333333;}#mermaid-svg-0NXm9hPJtzxjvR2X svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-0NXm9hPJtzxjvR2X p{margin:0;}#mermaid-svg-0NXm9hPJtzxjvR2X .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-0NXm9hPJtzxjvR2X .cluster-label text{fill:#333;}#mermaid-svg-0NXm9hPJtzxjvR2X .cluster-label span{color:#333;}#mermaid-svg-0NXm9hPJtzxjvR2X .cluster-label span p{background-color:transparent;}#mermaid-svg-0NXm9hPJtzxjvR2X .label text,#mermaid-svg-0NXm9hPJtzxjvR2X span{fill:#333;color:#333;}#mermaid-svg-0NXm9hPJtzxjvR2X .node rect,#mermaid-svg-0NXm9hPJtzxjvR2X .node circle,#mermaid-svg-0NXm9hPJtzxjvR2X .node ellipse,#mermaid-svg-0NXm9hPJtzxjvR2X .node polygon,#mermaid-svg-0NXm9hPJtzxjvR2X .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-0NXm9hPJtzxjvR2X .rough-node .label text,#mermaid-svg-0NXm9hPJtzxjvR2X .node .label text,#mermaid-svg-0NXm9hPJtzxjvR2X .image-shape .label,#mermaid-svg-0NXm9hPJtzxjvR2X .icon-shape .label{text-anchor:middle;}#mermaid-svg-0NXm9hPJtzxjvR2X .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-0NXm9hPJtzxjvR2X .rough-node .label,#mermaid-svg-0NXm9hPJtzxjvR2X .node .label,#mermaid-svg-0NXm9hPJtzxjvR2X .image-shape .label,#mermaid-svg-0NXm9hPJtzxjvR2X .icon-shape .label{text-align:center;}#mermaid-svg-0NXm9hPJtzxjvR2X .node.clickable{cursor:pointer;}#mermaid-svg-0NXm9hPJtzxjvR2X .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-0NXm9hPJtzxjvR2X .arrowheadPath{fill:#333333;}#mermaid-svg-0NXm9hPJtzxjvR2X .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-0NXm9hPJtzxjvR2X .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-0NXm9hPJtzxjvR2X .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0NXm9hPJtzxjvR2X .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-0NXm9hPJtzxjvR2X .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0NXm9hPJtzxjvR2X .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-0NXm9hPJtzxjvR2X .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-0NXm9hPJtzxjvR2X .cluster text{fill:#333;}#mermaid-svg-0NXm9hPJtzxjvR2X .cluster span{color:#333;}#mermaid-svg-0NXm9hPJtzxjvR2X div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-0NXm9hPJtzxjvR2X .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-0NXm9hPJtzxjvR2X rect.text{fill:none;stroke-width:0;}#mermaid-svg-0NXm9hPJtzxjvR2X .icon-shape,#mermaid-svg-0NXm9hPJtzxjvR2X .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-0NXm9hPJtzxjvR2X .icon-shape p,#mermaid-svg-0NXm9hPJtzxjvR2X .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-0NXm9hPJtzxjvR2X .icon-shape .label rect,#mermaid-svg-0NXm9hPJtzxjvR2X .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-0NXm9hPJtzxjvR2X .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-0NXm9hPJtzxjvR2X .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-0NXm9hPJtzxjvR2X :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} mvn verify

(springdoc-openapi-maven-plugin)
Controllers + DTOs
openapi.json

in build output
CI: diff against

last release
openapi-generator

TS / Kotlin / Swift clients
API portal /

gateway import

This diagram answers: once springdoc produces a spec, how does it feed the rest of the delivery pipeline?

springdoc-openapi-maven-plugin (1.5) starts the app during integration-test, downloads /v3/api-docs, and writes it to target/openapi.json; it needs spring-boot-maven-plugin's start/stop goals around it. A CI (Continuous Integration) job that diffs this file against the previous release (for example with openapi-diff) catches breaking changes --- a renamed field, a newly required parameter --- before clients do. (The plugin set-up is shown for orientation; it was not run for this article.)

13. Migrating from springfox

springfox springdoc / swagger-core
@EnableSwagger2, Docket bean Nothing to enable; GroupedOpenApi bean for groups
@Api @Tag
@ApiOperation(value = "...", notes = "...") @Operation(summary = "...", description = "...")
@ApiParam @Parameter
@ApiModel / @ApiModelProperty @Schema
@ApiResponse(code = 404, message = "...") @ApiResponse(responseCode = "404", description = "...")
@ApiIgnore @Hidden (or @Parameter(hidden = true))
/v2/api-docs, /swagger-ui/ /v3/api-docs, /swagger-ui.html

Remove every io.springfox dependency first --- having both on the classpath causes confusing start-up failures.

14. Common mistakes

Symptom Cause Fix
App fails at start with NoSuchMethodError / ClassNotFoundException springdoc line does not match the Spring Boot generation Use the version table in §4
/swagger-ui.html returns 401/403 Spring Security blocks the UI or its JSON Permit all three patterns in §10
UI loads but shows "Failed to load API definition" JSON path blocked, or a proxy rewrites paths Check /v3/api-docs/swagger-config and /v3/api-docs directly
Schema shows minLength: 0 on a @NotBlank field @Size without min @Size(min = 1, max = ...)
Documented 400 body doesn't match reality Default Boot error JSON, not ProblemDetail spring.mvc.problemdetails.enabled=true or a ResponseEntityExceptionHandler
Request DTO shows server-set fields like id One record used for both request and response Separate request/response DTOs, or accessMode = READ_ONLY
Code generator rejects the spec Tool only supports OAS 3.0 springdoc.api-docs.version=openapi_3_0

15. Conclusion

  • OpenAPI is the format; Swagger UI is the viewer; springdoc-openapi is the Spring integration. springfox is obsolete.
  • One starter dependency gives a live OpenAPI 3.1 document at /v3/api-docs and Swagger UI at /swagger-ui.html, derived from your actual mappings, types and validation rules.
  • Add value deliberately: an OpenAPI bean for metadata and security, @Operation/@ApiResponse for intent and error cases, @Schema for examples, GroupedOpenApi for large services --- and make the runtime error format match what you document.
  • Treat the spec as a product: lock it down or disable it in production, export it in CI, diff it between releases, and generate clients from it.

References

相关推荐
Rain的Java大神之路42 分钟前
面了上百人,我最爱问的LeetCode 1「两数之和」:能背代码的很多,能讲清“先查后存”的没几个
java·后端·算法
薛之谦_42 分钟前
Java 整合 RocketMQ 实战:从入门到复杂场景落地
java·rocketmq·java-rocketmq
aibo8603081 小时前
基于 Spring Boot + Vue3 的菜谱分享网站设计与实现
java·spring boot·后端
凤山老林1 小时前
Spring Boot 集成 TOTP 双因素认证与恢复码机制
java·spring boot·后端
小冯不疯1 小时前
物料主数据同步实战:从金蝶云星空到小满OKKICRM的单一策略深度拆解
java·大数据·数据库
vipxieliang1 小时前
Spring Boot 测试策略:单元测试、切片测试与集成测试
java·spring boot·后端
估值探索者1 小时前
【Python量化技术指标实战 04】布林带通道突破,怎么知道是真突破还是假突破?
java·开发语言·python·接口·股票数据api接口
未秃头的程序猿1 小时前
同一个商户号,V2 免密代扣和 V3 分账的证书怎么共存?
java·后端·微信
captain3761 小时前
Spring Boot的配置文件
java·spring boot·后端