openapi_docs

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Spring Boot OpenAPI Documentation with SpringDoc

基于SpringDoc的Spring Boot OpenAPI文档方案

Implement comprehensive REST API documentation using SpringDoc OpenAPI 3.0 and Swagger UI in Spring Boot 3.x applications.
在Spring Boot 3.x应用中使用SpringDoc OpenAPI 3.0和Swagger UI实现完善的REST API文档。

When to Use

适用场景

Use this skill when you need to:
  • Set up SpringDoc OpenAPI in Spring Boot 3.x projects
  • Generate OpenAPI 3.0 specifications for REST APIs
  • Configure and customize Swagger UI
  • Add detailed API documentation with annotations
  • Document request/response models with validation
  • Implement API security documentation (JWT, OAuth2, Basic Auth)
  • Document pageable and sortable endpoints
  • Add examples and schemas to API endpoints
  • Customize OpenAPI definitions programmatically
  • Generate API documentation for WebMvc or WebFlux applications
  • Support multiple API groups and versions
  • Document error responses and exception handlers
  • Add JSR-303 Bean Validation to API documentation
  • Support Kotlin-based Spring Boot APIs
当你需要完成以下操作时可使用该方案:
  • 在Spring Boot 3.x项目中搭建SpringDoc OpenAPI
  • 为REST API生成OpenAPI 3.0规范文档
  • 配置和自定义Swagger UI
  • 通过注解添加详细的API文档说明
  • 为请求/响应模型添加校验规则文档
  • 实现API安全相关文档(JWT、OAuth2、基础认证)
  • 为支持分页、排序的端点编写文档
  • 为API端点添加示例和Schema定义
  • 通过编程方式自定义OpenAPI定义
  • 为WebMvc或WebFlux应用生成API文档
  • 支持多API分组和多版本管理
  • 为错误响应和异常处理器编写文档
  • 在API文档中集成JSR-303 Bean Validation规则
  • 支持基于Kotlin开发的Spring Boot API

Setup Dependencies

依赖安装

Add Maven Dependencies

添加Maven依赖

xml
<!-- Standard WebMVC support -->
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
    <version>2.8.13</version> // Use latest stable version
</dependency>

<!-- Optional: therapi-runtime-javadoc for JavaDoc support -->
<dependency>
    <groupId>com.github.therapi</groupId>
    <artifactId>therapi-runtime-javadoc</artifactId>
    <version>0.15.0</version> // Use latest stable version
    <scope>provided</scope>
</dependency>

<!-- WebFlux support -->
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
    <version>2.8.13</version> // Use latest stable version
</dependency>
xml
<!-- 标准WebMVC支持 -->
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
    <version>2.8.13</version> // Use latest stable version
</dependency>

<!-- 可选:therapi-runtime-javadoc 用于支持JavaDoc导入 -->
<dependency>
    <groupId>com.github.therapi</groupId>
    <artifactId>therapi-runtime-javadoc</artifactId>
    <version>0.15.0</version> // Use latest stable version
    <scope>provided</scope>
</dependency>

<!-- WebFlux支持 -->
<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webflux-ui</artifactId>
    <version>2.8.13</version> // Use latest stable version
</dependency>

Add Gradle Dependencies

添加Gradle依赖

gradle
// Standard WebMVC support
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.13'

// Optional: therapi-runtime-javadoc for JavaDoc support
implementation 'com.github.therapi:therapi-runtime-javadoc:0.15.0'

// WebFlux support
implementation 'org.springdoc:springdoc-openapi-starter-webflux-ui:2.8.13'
gradle
// 标准WebMVC支持
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.13'

// 可选:therapi-runtime-javadoc 用于支持JavaDoc导入
implementation 'com.github.therapi:therapi-runtime-javadoc:0.15.0'

// WebFlux支持
implementation 'org.springdoc:springdoc-openapi-starter-webflux-ui:2.8.13'

Configure SpringDoc

SpringDoc配置

Basic Configuration

基础配置

properties
undefined
properties
undefined

application.properties

application.properties

springdoc.api-docs.path=/api-docs springdoc.swagger-ui.path=/swagger-ui-custom.html springdoc.swagger-ui.operationsSorter=method springdoc.swagger-ui.tagsSorter=alpha springdoc.swagger-ui.enabled=true springdoc.api-docs.enabled=true springdoc.packages-to-scan=com.example.controller springdoc.paths-to-match=/api/**

```yaml
springdoc.api-docs.path=/api-docs springdoc.swagger-ui.path=/swagger-ui-custom.html springdoc.swagger-ui.operationsSorter=method springdoc.swagger-ui.tagsSorter=alpha springdoc.swagger-ui.enabled=true springdoc.api-docs.enabled=true springdoc.packages-to-scan=com.example.controller springdoc.paths-to-match=/api/**

```yaml

application.yml

application.yml

springdoc: api-docs: path: /api-docs enabled: true swagger-ui: path: /swagger-ui.html enabled: true operationsSorter: method tagsSorter: alpha tryItOutEnabled: true packages-to-scan: com.example.controller paths-to-match: /api/**
undefined
springdoc: api-docs: path: /api-docs enabled: true swagger-ui: path: /swagger-ui.html enabled: true operationsSorter: method tagsSorter: alpha tryItOutEnabled: true packages-to-scan: com.example.controller paths-to-match: /api/**
undefined

Access Endpoints

访问端点

After configuration:
  • OpenAPI JSON:
    http://localhost:8080/v3/api-docs
  • OpenAPI YAML:
    http://localhost:8080/v3/api-docs.yaml
  • Swagger UI:
    http://localhost:8080/swagger-ui/index.html
配置完成后可以访问以下地址:
  • OpenAPI JSON文档:
    http://localhost:8080/v3/api-docs
  • OpenAPI YAML文档:
    http://localhost:8080/v3/api-docs.yaml
  • Swagger UI界面:
    http://localhost:8080/swagger-ui/index.html

Document Controllers

控制器文档编写

Basic Controller Documentation

基础控制器文档

java
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
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.web.bind.annotation.*;

@RestController
@RequestMapping("/api/books")
@Tag(name = "Book", description = "Book management APIs")
public class BookController {

    @Operation(
        summary = "Retrieve a book by ID",
        description = "Get a Book object by specifying its ID. The response includes id, title, author and description."
    )
    @ApiResponses(value = {
        @ApiResponse(
            responseCode = "200",
            description = "Successfully retrieved book",
            content = @Content(schema = @Schema(implementation = Book.class))
        ),
        @ApiResponse(
            responseCode = "404",
            description = "Book not found"
        )
    })
    @GetMapping("/{id}")
    public Book findById(
        @Parameter(description = "ID of book to retrieve", required = true)
        @PathVariable Long id
    ) {
        return repository.findById(id)
            .orElseThrow(() -> new BookNotFoundException());
    }
}
java
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
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.web.bind.annotation.*;

@RestController
@RequestMapping("/api/books")
@Tag(name = "Book", description = "Book management APIs")
public class BookController {

    @Operation(
        summary = "Retrieve a book by ID",
        description = "Get a Book object by specifying its ID. The response includes id, title, author and description."
    )
    @ApiResponses(value = {
        @ApiResponse(
            responseCode = "200",
            description = "Successfully retrieved book",
            content = @Content(schema = @Schema(implementation = Book.class))
        ),
        @ApiResponse(
            responseCode = "404",
            description = "Book not found"
        )
    })
    @GetMapping("/{id}")
    public Book findById(
        @Parameter(description = "ID of book to retrieve", required = true)
        @PathVariable Long id
    ) {
        return repository.findById(id)
            .orElseThrow(() -> new BookNotFoundException());
    }
}

Document Request Bodies

请求体文档编写

java
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.media.ExampleObject;

@Operation(summary = "Create a new book")
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Book createBook(
    @RequestBody(
        description = "Book to create",
        required = true,
        content = @Content(
            schema = @Schema(implementation = Book.class),
            examples = @ExampleObject(
                value = """
                {
                    "title": "Clean Code",
                    "author": "Robert C. Martin",
                    "isbn": "978-0132350884",
                    "description": "A handbook of agile software craftsmanship"
                }
                """
            )
        )
    )
    Book book
) {
    return repository.save(book);
}
java
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.media.ExampleObject;

@Operation(summary = "Create a new book")
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public Book createBook(
    @RequestBody(
        description = "Book to create",
        required = true,
        content = @Content(
            schema = @Schema(implementation = Book.class),
            examples = @ExampleObject(
                value = """
                {
                    "title": "Clean Code",
                    "author": "Robert C. Martin",
                    "isbn": "978-0132350884",
                    "description": "A handbook of agile software craftsmanship"
                }
                """
            )
        )
    )
    Book book
) {
    return repository.save(book);
}

Document Models

模型文档编写

Entity with Validation

带校验规则的实体类

java
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.*;

@Entity
@Schema(description = "Book entity representing a published book")
public class Book {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Schema(description = "Unique identifier", example = "1", accessMode = Schema.AccessMode.READ_ONLY)
    private Long id;

    @NotBlank(message = "Title is required")
    @Size(min = 1, max = 200)
    @Schema(description = "Book title", example = "Clean Code", required = true, maxLength = 200)
    private String title;

    @Pattern(regexp = "^(?:ISBN(?:-1[03])?:? )?(?=[0-9X]{10}$|(?=(?:[0-9]+[- ]){3})[- 0-9X]{13}$|97[89][0-9]{10}$|(?=(?:[0-9]+[- ]){4})[- 0-9]{17}$)(?:97[89][- ]?)?[0-9]{1,5}[- ]?[0-9]+[- ]?[0-9]+[- ]?[0-9X]$")
    @Schema(description = "ISBN number", example = "978-0132350884")
    private String isbn;

    // Additional fields, constructors, getters, setters
}
java
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.*;

@Entity
@Schema(description = "Book entity representing a published book")
public class Book {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Schema(description = "Unique identifier", example = "1", accessMode = Schema.AccessMode.READ_ONLY)
    private Long id;

    @NotBlank(message = "Title is required")
    @Size(min = 1, max = 200)
    @Schema(description = "Book title", example = "Clean Code", required = true, maxLength = 200)
    private String title;

    @Pattern(regexp = "^(?:ISBN(?:-1[03])?:? )?(?=[0-9X]{10}$|(?=(?:[0-9]+[- ]){3})[- 0-9X]{13}$|97[89][0-9]{10}$|(?=(?:[0-9]+[- ]){4})[- 0-9]{17}$)(?:97[89][- ]?)?[0-9]{1,5}[- ]?[0-9]+[- ]?[0-9]+[- ]?[0-9X]$")
    @Schema(description = "ISBN number", example = "978-0132350884")
    private String isbn;

    // Additional fields, constructors, getters, setters
}

Hidden Fields

隐藏字段

java
@Schema(hidden = true)
private String internalField;

@JsonIgnore
@Schema(accessMode = Schema.AccessMode.READ_ONLY)
private LocalDateTime createdAt;
java
@Schema(hidden = true)
private String internalField;

@JsonIgnore
@Schema(accessMode = Schema.AccessMode.READ_ONLY)
private LocalDateTime createdAt;

Document Security

安全相关文档

JWT Bearer Authentication

JWT Bearer认证配置

java
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.security.SecurityScheme;

@Configuration
public class OpenAPISecurityConfig {

    @Bean
    public OpenAPI customOpenAPI() {
        return new OpenAPI()
            .components(new Components()
                .addSecuritySchemes("bearer-jwt", new SecurityScheme()
                    .type(SecurityScheme.Type.HTTP)
                    .scheme("bearer")
                    .bearerFormat("JWT")
                    .description("JWT authentication")
                )
            );
    }
}

// Apply security requirement
@RestController
@RequestMapping("/api/books")
@SecurityRequirement(name = "bearer-jwt")
public class BookController {
    // All endpoints require JWT authentication
}
java
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.security.SecurityScheme;

@Configuration
public class OpenAPISecurityConfig {

    @Bean
    public OpenAPI customOpenAPI() {
        return new OpenAPI()
            .components(new Components()
                .addSecuritySchemes("bearer-jwt", new SecurityScheme()
                    .type(SecurityScheme.Type.HTTP)
                    .scheme("bearer")
                    .bearerFormat("JWT")
                    .description("JWT authentication")
                )
            );
    }
}

// 应用安全规则
@RestController
@RequestMapping("/api/books")
@SecurityRequirement(name = "bearer-jwt")
public class BookController {
    // 所有端点都需要JWT认证
}

OAuth2 Configuration

OAuth2配置

java
import io.swagger.v3.oas.models.security.OAuthFlow;
import io.swagger.v3.oas.models.security.OAuthFlows;
import io.swagger.v3.oas.models.security.Scopes;

@Bean
public OpenAPI customOpenAPI() {
    return new OpenAPI()
        .components(new Components()
            .addSecuritySchemes("oauth2", new SecurityScheme()
                .type(SecurityScheme.Type.OAUTH2)
                .flows(new OAuthFlows()
                    .authorizationCode(new OAuthFlow()
                        .authorizationUrl("https://auth.example.com/oauth/authorize")
                        .tokenUrl("https://auth.example.com/oauth/token")
                        .scopes(new Scopes()
                            .addString("read", "Read access")
                            .addString("write", "Write access")
                        )
                    )
                )
            )
        );
}
java
import io.swagger.v3.oas.models.security.OAuthFlow;
import io.swagger.v3.oas.models.security.OAuthFlows;
import io.swagger.v3.oas.models.security.Scopes;

@Bean
public OpenAPI customOpenAPI() {
    return new OpenAPI()
        .components(new Components()
            .addSecuritySchemes("oauth2", new SecurityScheme()
                .type(SecurityScheme.Type.OAUTH2)
                .flows(new OAuthFlows()
                    .authorizationCode(new OAuthFlow()
                        .authorizationUrl("https://auth.example.com/oauth/authorize")
                        .tokenUrl("https://auth.example.com/oauth/token")
                        .scopes(new Scopes()
                            .addString("read", "Read access")
                            .addString("write", "Write access")
                        )
                    )
                )
            )
        );
}

Document Pagination

分页功能文档

Spring Data Pageable Support

Spring Data Pageable支持

java
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

@Operation(summary = "Get paginated list of books")
@GetMapping("/paginated")
public Page<Book> findAllPaginated(
    @ParameterObject Pageable pageable
) {
    return repository.findAll(pageable);
}
java
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

@Operation(summary = "Get paginated list of books")
@GetMapping("/paginated")
public Page<Book> findAllPaginated(
    @ParameterObject Pageable pageable
) {
    return repository.findAll(pageable);
}

Advanced Configuration

高级配置

Multiple API Groups

多API分组

java
import org.springdoc.core.models.GroupedOpenApi;

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

@Bean
public GroupedOpenApi adminApi() {
    return GroupedOpenApi.builder()
        .group("admin")
        .pathsToMatch("/api/admin/**")
        .build();
}
java
import org.springdoc.core.models.GroupedOpenApi;

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

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

Custom Operation Customizer

自定义操作定制器

java
import org.springdoc.core.customizers.OperationCustomizer;

@Bean
public OperationCustomizer customizeOperation() {
    return (operation, handlerMethod) -> {
        operation.addExtension("x-custom-field", "custom-value");
        return operation;
    };
}
java
import org.springdoc.core.customizers.OperationCustomizer;

@Bean
public OperationCustomizer customizeOperation() {
    return (operation, handlerMethod) -> {
        operation.addExtension("x-custom-field", "custom-value");
        return operation;
    };
}

Hide Endpoints

隐藏端点

java
@Operation(hidden = true)
@GetMapping("/internal")
public String internalEndpoint() {
    return "Hidden from docs";
}

// Hide entire controller
@Hidden
@RestController
public class InternalController {
    // All endpoints hidden
}
java
@Operation(hidden = true)
@GetMapping("/internal")
public String internalEndpoint() {
    return "Hidden from docs";
}

// 隐藏整个控制器
@Hidden
@RestController
public class InternalController {
    // 所有端点都会被隐藏
}

Document Exception Responses

异常响应文档

Global Exception Handler

全局异常处理器

java
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(BookNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    @Operation(hidden = true)
    public ErrorResponse handleBookNotFound(BookNotFoundException ex) {
        return new ErrorResponse("BOOK_NOT_FOUND", ex.getMessage());
    }

    @ExceptionHandler(ValidationException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @Operation(hidden = true)
    public ErrorResponse handleValidation(ValidationException ex) {
        return new ErrorResponse("VALIDATION_ERROR", ex.getMessage());
    }
}

@Schema(description = "Error response")
public record ErrorResponse(
    @Schema(description = "Error code", example = "BOOK_NOT_FOUND")
    String code,

    @Schema(description = "Error message", example = "Book with ID 123 not found")
    String message,

    @Schema(description = "Timestamp", example = "2024-01-15T10:30:00Z")
    LocalDateTime timestamp
) {}
java
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(BookNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    @Operation(hidden = true)
    public ErrorResponse handleBookNotFound(BookNotFoundException ex) {
        return new ErrorResponse("BOOK_NOT_FOUND", ex.getMessage());
    }

    @ExceptionHandler(ValidationException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @Operation(hidden = true)
    public ErrorResponse handleValidation(ValidationException ex) {
        return new ErrorResponse("VALIDATION_ERROR", ex.getMessage());
    }
}

@Schema(description = "Error response")
public record ErrorResponse(
    @Schema(description = "Error code", example = "BOOK_NOT_FOUND")
    String code,

    @Schema(description = "Error message", example = "Book with ID 123 not found")
    String message,

    @Schema(description = "Timestamp", example = "2024-01-15T10:30:00Z")
    LocalDateTime timestamp
) {}

Build Integration

构建集成

Maven Plugin

Maven插件

xml
<plugin>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-maven-plugin</artifactId>
    <version>1.4</version>
    <executions>
        <execution>
            <phase>integration-test</phase>
            <goals>
                <goal>generate</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <apiDocsUrl>http://localhost:8080/v3/api-docs</apiDocsUrl>
        <outputFileName>openapi.json</outputFileName>
        <outputDir>${project.build.directory}</outputDir>
    </configuration>
</plugin>
xml
<plugin>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-maven-plugin</artifactId>
    <version>1.4</version>
    <executions>
        <execution>
            <phase>integration-test</phase>
            <goals>
                <goal>generate</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <apiDocsUrl>http://localhost:8080/v3/api-docs</apiDocsUrl>
        <outputFileName>openapi.json</outputFileName>
        <outputDir>${project.build.directory}</outputDir>
    </configuration>
</plugin>

Gradle Plugin

Gradle插件

gradle
plugins {
    id 'org.springdoc.openapi-gradle-plugin' version '1.9.0'
}

openApi {
    apiDocsUrl = "http://localhost:8080/v3/api-docs"
    outputDir = file("$buildDir/docs")
    outputFileName = "openapi.json"
}
gradle
plugins {
    id 'org.springdoc.openapi-gradle-plugin' version '1.9.0'
}

openApi {
    apiDocsUrl = "http://localhost:8080/v3/api-docs"
    outputDir = file("$buildDir/docs")
    outputFileName = "openapi.json"
}

Examples

示例

Complete REST Controller Example

完整REST控制器示例

java
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
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 io.swagger.v3.oas.annotations.security.SecurityRequirement;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.web.bind.annotation.*;
import jakarta.validation.Valid;

@RestController
@RequestMapping("/api/books")
@Tag(name = "Book", description = "Book management APIs")
@SecurityRequirement(name = "bearer-jwt")
public class BookController {

    private final BookService bookService;

    public BookController(BookService bookService) {
        this.bookService = bookService;
    }

    @Operation(summary = "Get all books")
    @ApiResponses(value = {
        @ApiResponse(
            responseCode = "200",
            description = "Found all books",
            content = @Content(
                mediaType = "application/json",
                array = @ArraySchema(schema = @Schema(implementation = Book.class))
            )
        )
    })
    @GetMapping
    public List<Book> getAllBooks() {
        return bookService.getAllBooks();
    }

    @Operation(summary = "Get paginated books")
    @GetMapping("/paginated")
    public Page<Book> getBooksPaginated(@ParameterObject Pageable pageable) {
        return bookService.getBooksPaginated(pageable);
    }

    @Operation(summary = "Get book by ID")
    @ApiResponses(value = {
        @ApiResponse(responseCode = "200", description = "Book found"),
        @ApiResponse(responseCode = "404", description = "Book not found")
    })
    @GetMapping("/{id}")
    public Book getBookById(@PathVariable Long id) {
        return bookService.getBookById(id);
    }

    @Operation(summary = "Create new book")
    @ApiResponses(value = {
        @ApiResponse(responseCode = "201", description = "Book created successfully"),
        @ApiResponse(responseCode = "400", description = "Invalid input")
    })
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Book createBook(@Valid @RequestBody Book book) {
        return bookService.createBook(book);
    }

    @Operation(summary = "Update book")
    @ApiResponses(value = {
        @ApiResponse(responseCode = "200", description = "Book updated"),
        @ApiResponse(responseCode = "404", description = "Book not found")
    })
    @PutMapping("/{id}")
    public Book updateBook(@PathVariable Long id, @Valid @RequestBody Book book) {
        return bookService.updateBook(id, book);
    }

    @Operation(summary = "Delete book")
    @ApiResponses(value = {
        @ApiResponse(responseCode = "204", description = "Book deleted"),
        @ApiResponse(responseCode = "404", description = "Book not found")
    })
    @DeleteMapping("/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void deleteBook(@PathVariable Long id) {
        bookService.deleteBook(id);
    }
}
java
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
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 io.swagger.v3.oas.annotations.security.SecurityRequirement;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.web.bind.annotation.*;
import jakarta.validation.Valid;

@RestController
@RequestMapping("/api/books")
@Tag(name = "Book", description = "Book management APIs")
@SecurityRequirement(name = "bearer-jwt")
public class BookController {

    private final BookService bookService;

    public BookController(BookService bookService) {
        this.bookService = bookService;
    }

    @Operation(summary = "Get all books")
    @ApiResponses(value = {
        @ApiResponse(
            responseCode = "200",
            description = "Found all books",
            content = @Content(
                mediaType = "application/json",
                array = @ArraySchema(schema = @Schema(implementation = Book.class))
            )
        )
    })
    @GetMapping
    public List<Book> getAllBooks() {
        return bookService.getAllBooks();
    }

    @Operation(summary = "Get paginated books")
    @GetMapping("/paginated")
    public Page<Book> getBooksPaginated(@ParameterObject Pageable pageable) {
        return bookService.getBooksPaginated(pageable);
    }

    @Operation(summary = "Get book by ID")
    @ApiResponses(value = {
        @ApiResponse(responseCode = "200", description = "Book found"),
        @ApiResponse(responseCode = "404", description = "Book not found")
    })
    @GetMapping("/{id}")
    public Book getBookById(@PathVariable Long id) {
        return bookService.getBookById(id);
    }

    @Operation(summary = "Create new book")
    @ApiResponses(value = {
        @ApiResponse(responseCode = "201", description = "Book created successfully"),
        @ApiResponse(responseCode = "400", description = "Invalid input")
    })
    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Book createBook(@Valid @RequestBody Book book) {
        return bookService.createBook(book);
    }

    @Operation(summary = "Update book")
    @ApiResponses(value = {
        @ApiResponse(responseCode = "200", description = "Book updated"),
        @ApiResponse(responseCode = "404", description = "Book not found")
    })
    @PutMapping("/{id}")
    public Book updateBook(@PathVariable Long id, @Valid @RequestBody Book book) {
        return bookService.updateBook(id, book);
    }

    @Operation(summary = "Delete book")
    @ApiResponses(value = {
        @ApiResponse(responseCode = "204", description = "Book deleted"),
        @ApiResponse(responseCode = "404", description = "Book not found")
    })
    @DeleteMapping("/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void deleteBook(@PathVariable Long id) {
        bookService.deleteBook(id);
    }
}

Best Practices

最佳实践

  1. Use descriptive operation summaries and descriptions
    • Summary: Short, clear statement (< 120 chars)
    • Description: Detailed explanation with use cases
  2. Document all response codes
    • Include success (2xx), client errors (4xx), server errors (5xx)
    • Provide meaningful descriptions for each
  3. Add examples to request/response bodies
    • Use
      @ExampleObject
      for realistic examples
    • Include edge cases when relevant
  4. Leverage JSR-303 validation annotations
    • SpringDoc auto-generates constraints from validation annotations
    • Reduces duplication between code and documentation
  5. Use
    @ParameterObject
    for complex parameters
    • Especially useful for Pageable, custom filter objects
    • Keeps controller methods clean
  6. Group related endpoints with @Tag
    • Organize API by domain entities or features
    • Use consistent tag names across controllers
  7. Document security requirements
    • Apply
      @SecurityRequirement
      where authentication needed
    • Configure security schemes globally in OpenAPI bean
  8. Hide internal/admin endpoints appropriately
    • Use
      @Hidden
      or create separate API groups
    • Prevent exposing internal implementation details
  9. Customize Swagger UI for better UX
    • Enable filtering, sorting, try-it-out features
    • Set appropriate default behaviors
  10. Version your API documentation
    • Include version in OpenAPI Info
    • Consider multiple API groups for versioned APIs
  1. 使用清晰的操作摘要和描述
    • 摘要:简短明确的说明(不超过120个字符)
    • 描述:包含使用场景的详细解释
  2. 为所有响应码编写文档
    • 包含成功响应(2xx)、客户端错误(4xx)、服务端错误(5xx)
    • 为每个响应码提供有意义的说明
  3. 为请求/响应体添加示例
    • 使用
      @ExampleObject
      提供真实可用的示例
    • 相关场景下补充边界 case 示例
  4. 复用JSR-303校验注解
    • SpringDoc会自动从校验注解生成约束规则
    • 减少代码和文档之间的重复定义
  5. 复杂参数使用
    @ParameterObject
    注解
    • 尤其适合Pageable、自定义过滤对象等场景
    • 保持控制器方法代码整洁
  6. 使用
    @Tag
    对相关端点分组
    • 按领域实体或功能模块组织API
    • 跨控制器使用统一的标签名称
  7. 文档化安全要求
    • 需要认证的接口添加
      @SecurityRequirement
      注解
    • 在OpenAPI Bean中全局配置安全方案
  8. 合理隐藏内部/管理端端点
    • 使用
      @Hidden
      注解或者创建独立的API分组
    • 避免暴露内部实现细节
  9. 自定义Swagger UI提升用户体验
    • 开启过滤、排序、调试功能
    • 设置合适的默认行为
  10. 为API文档添加版本管理
    • 在OpenAPI Info中包含版本信息
    • 多版本API可考虑使用多分组管理

Common Annotations Reference

常用注解参考

Core Annotations

核心注解

  • @Tag
    : Group operations under a tag
  • @Operation
    : Describe a single API operation
  • @ApiResponse
    /
    @ApiResponses
    : Document response codes
  • @Parameter
    : Document a single parameter
  • @RequestBody
    : Document request body (OpenAPI version)
  • @Schema
    : Document model schema
  • @SecurityRequirement
    : Apply security to operations
  • @Hidden
    : Hide from documentation
  • @ParameterObject
    : Document complex objects as parameters
  • @Tag
    : 为接口操作分组
  • @Operation
    : 描述单个API操作
  • @ApiResponse
    /
    @ApiResponses
    : 文档化响应码
  • @Parameter
    : 描述单个参数
  • @RequestBody
    : 描述请求体(OpenAPI版本注解)
  • @Schema
    : 描述模型Schema
  • @SecurityRequirement
    : 为操作应用安全规则
  • @Hidden
    : 从文档中隐藏对应内容
  • @ParameterObject
    : 将复杂对象转为参数描述

Validation Annotations (Auto-documented)

自动文档化的校验注解

  • @NotNull
    ,
    @NotBlank
    ,
    @NotEmpty
    : Required fields
  • @Size(min, max)
    : String/collection length constraints
  • @Min
    ,
    @Max
    : Numeric range constraints
  • @Pattern
    : Regex validation
  • @Email
    : Email validation
  • @DecimalMin
    ,
    @DecimalMax
    : Decimal constraints
  • @Positive
    ,
    @PositiveOrZero
    ,
    @Negative
    ,
    @NegativeOrZero
  • @NotNull
    ,
    @NotBlank
    ,
    @NotEmpty
    : 必填字段
  • @Size(min, max)
    : 字符串/集合长度约束
  • @Min
    ,
    @Max
    : 数值范围约束
  • @Pattern
    : 正则校验
  • @Email
    : 邮箱格式校验
  • @DecimalMin
    ,
    @DecimalMax
    : 小数范围约束
  • @Positive
    ,
    @PositiveOrZero
    ,
    @Negative
    ,
    @NegativeOrZero
    : 正负数值约束

Troubleshooting

问题排查

For common issues and solutions, refer to the troubleshooting guide in @references/troubleshooting.md
常见问题和解决方案可参考@references/troubleshooting.md中的排查指南

Related Skills

相关技能

  • spring-boot-rest-api-standards
    - REST API design standards
  • spring-boot-dependency-injection
    - Dependency injection patterns
  • unit-test-controller-layer
    - Testing REST controllers
  • spring-boot-actuator
    - Production monitoring and management
  • spring-boot-rest-api-standards
    - REST API设计规范
  • spring-boot-dependency-injection
    - 依赖注入模式
  • unit-test-controller-layer
    - REST控制器测试
  • spring-boot-actuator
    - 生产环境监控与管理

References

参考资料

🔄 Workflow

🔄 工作流

Aşama 1: Configuration

阶段1:配置

  • Dependency:
    springdoc-openapi-starter-webmvc-ui
    (v2.x for Spring Boot 3) ekle.
  • Properties:
    springdoc.api-docs.path
    ve
    swagger-ui.path
    değerlerini sabitle (custom path kullanıyorsan).
  • Platform: WebMVC vs WebFlux ayrımına dikkat et (dependency farklı).
  • 依赖: 添加
    springdoc-openapi-starter-webmvc-ui
    (Spring Boot 3对应v2.x版本)。
  • 配置项: 固定
    springdoc.api-docs.path
    swagger-ui.path
    的值(如果使用自定义路径)。
  • 平台适配: 注意区分WebMVC和WebFlux,二者依赖不同。

Aşama 2: Documentation Layer

阶段2:文档层开发

  • Controller:
    @Tag
    ile grupla,
    @Operation
    ile her endpoint'i açıkla.
  • Models: DTO'ları
    @Schema
    ile tanımla, validation anotasyonlarını (
    @NotNull
    ) ekle (otomatik yansır).
  • Security: Global security scheme (JWT/OAuth2) tanımla ve endpoint'lere
    @SecurityRequirement
    ekle.
  • 控制器: 用
    @Tag
    分组,用
    @Operation
    说明每个端点的作用。
  • 模型: 用
    @Schema
    定义DTO,添加校验注解(如
    @NotNull
    ),规则会自动同步到文档。
  • 安全配置: 定义全局安全方案(JWT/OAuth2),为对应端点添加
    @SecurityRequirement
    注解。

Aşama 3: Enhancement

阶段3:优化增强

  • Examples:
    @ExampleObject
    kullanarak request/response body'ler için gerçekçi örnekler ver.
  • Error Handling: Global Exception Handler'daki hata response formatlarını
    @ApiResponse
    ile dokümante et.
  • Generation: CI/CD pipeline'ında
    springdoc-openapi-maven-plugin
    ile
    openapi.json
    üret.
  • 示例补充: 使用
    @ExampleObject
    为请求/响应体提供真实可用的示例。
  • 错误处理: 通过
    @ApiResponse
    文档化全局异常处理器的错误响应格式。
  • 自动生成: 在CI/CD流水线中通过
    springdoc-openapi-maven-plugin
    生成
    openapi.json
    文件。

Kontrol Noktaları

检查点

AşamaDoğrulama
1
/v3/api-docs
JSON dönüyor mu?
2Swagger UI'da "Try it out" butonu çalışıyor mu (CORS/Auth sorunu var mı)?
3Enum değerleri ve required alanlar dokümanda doğru görünüyor mu?
阶段验证项
1
/v3/api-docs
是否正常返回JSON?
2Swagger UI的「Try it out」按钮是否正常工作?是否存在CORS/权限问题?
3枚举值和必填字段在文档中是否展示正确?