spring-boot-openapi-documentation

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
<!-- 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>

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
// 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'

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")
                )
            );
    }
}

// Apply security requirement
@RestController
@RequestMapping("/api/books")
@SecurityRequirement(name = "bearer-jwt")
public class BookController {
    // All endpoints require JWT authentication
}

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";
}

// Hide entire controller
@Hidden
@RestController
public class InternalController {
    // All endpoints hidden
}

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
      添加真实示例
    • 必要时包含边缘情况
  4. 利用JSR-303验证注解
    • SpringDoc会自动从验证注解生成约束规则
    • 减少代码和文档的重复
  5. 对复杂参数使用
    @ParameterObject
    • 尤其适用于Pageable、自定义过滤对象
    • 保持控制器方法简洁
  6. 使用@Tag对相关端点分组
    • 按领域实体或功能组织API
    • 在控制器间使用一致的标签名称
  7. 记录安全要求
    • 在需要认证的地方应用
      @SecurityRequirement
    • 在OpenAPI Bean中全局配置安全方案
  8. 适当隐藏内部/管理员端点
    • 使用
      @Hidden
      或创建单独的API分组
    • 避免暴露内部实现细节
  9. 自定义Swagger UI以提升用户体验
    • 启用过滤、排序、在线调试(try-it-out)功能
    • 设置合适的默认行为
  10. 为API文档添加版本
    • 在OpenAPI信息中包含版本
    • 考虑为版本化API创建多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

参考资料