spring-boot-rest-api-standards

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Spring Boot REST API Standards

Spring Boot REST API 设计标准

This skill provides comprehensive guidance for building RESTful APIs in Spring Boot applications with consistent design patterns, proper error handling, validation, and architectural best practices based on REST principles and Spring Boot conventions.
本技能基于REST原则与Spring Boot约定,为在Spring Boot应用中构建RESTful API提供全面指导,涵盖一致的设计模式、规范的错误处理、校验机制及架构最佳实践。

Overview

概述

Spring Boot REST API standards establish consistent patterns for building production-ready REST APIs. These standards cover resource-based URL design, proper HTTP method usage, status code conventions, DTO patterns, validation, error handling, pagination, security headers, and architectural layering. Implement these patterns to ensure API consistency, maintainability, and adherence to REST principles.
Spring Boot REST API设计标准为构建可用于生产环境的REST API确立了统一模式。这些标准涵盖基于资源的URL设计、规范的HTTP方法使用、状态码约定、DTO模式、校验、错误处理、分页、安全标头及架构分层。遵循这些模式可确保API的一致性、可维护性,并符合REST原则。

When to Use This Skill

适用场景

Use this skill when:
  • Creating new REST endpoints and API routes
  • Designing request/response DTOs and API contracts
  • Planning HTTP methods and status codes
  • Implementing error handling and validation
  • Setting up pagination, filtering, and sorting
  • Designing security headers and CORS policies
  • Implementing HATEOAS (Hypermedia As The Engine Of Application State)
  • Reviewing REST API architecture and design patterns
  • Building microservices with consistent API standards
  • Documenting API endpoints with clear contracts
本技能适用于以下场景:
  • 创建新的REST端点与API路由
  • 设计请求/响应DTO与API契约
  • 规划HTTP方法与状态码
  • 实现错误处理与校验机制
  • 配置分页、过滤与排序
  • 设计安全标头与CORS策略
  • 实现HATEOAS(超媒体作为应用状态引擎)
  • 评审REST API架构与设计模式
  • 构建遵循统一API标准的微服务
  • 编写清晰契约的API端点文档

Instructions

操作指南

To Build RESTful API Endpoints

构建RESTful API端点步骤

Follow these steps to create well-designed REST API endpoints:
  1. Design Resource-Based URLs
    • Use plural nouns for resource names
    • Follow REST conventions: GET /users, POST /users, PUT /users/{id}
    • Avoid action-based URLs like /getUserList
  2. Implement Proper HTTP Methods
    • GET: Retrieve resources (safe, idempotent)
    • POST: Create resources (not idempotent)
    • PUT: Replace entire resources (idempotent)
    • PATCH: Partial updates (not idempotent)
    • DELETE: Remove resources (idempotent)
  3. Use Appropriate Status Codes
    • 200 OK: Successful GET/PUT/PATCH
    • 201 Created: Successful POST with Location header
    • 204 No Content: Successful DELETE
    • 400 Bad Request: Invalid request data
    • 404 Not Found: Resource doesn't exist
    • 409 Conflict: Duplicate resource
    • 500 Internal Server Error: Unexpected errors
  4. Create Request/Response DTOs
    • Separate API contracts from domain entities
    • Use Java records or Lombok
      @Data
      /
      @Value
    • Apply Jakarta validation annotations
    • Keep DTOs immutable when possible
  5. Implement Validation
    • Use
      @Valid
      annotation on
      @RequestBody
      parameters
    • Apply validation constraints (
      @NotBlank
      ,
      @Email
      ,
      @Size
      , etc.)
    • Handle validation errors with
      MethodArgumentNotValidException
  6. Set Up Error Handling
    • Use
      @RestControllerAdvice
      for global exception handling
    • Return standardized error responses with status, error, message, and timestamp
    • Use
      ResponseStatusException
      for specific HTTP status codes
  7. Configure Pagination
    • Use Pageable for large datasets
    • Include page, size, sort parameters
    • Return metadata with total elements, totalPages, etc.
  8. Add Security Headers
    • Configure CORS policies
    • Set content security policy
    • Include X-Frame-Options, X-Content-Type-Options
遵循以下步骤创建设计优良的REST API端点:
  1. 设计基于资源的URL
    • 资源名称使用复数名词
    • 遵循REST约定:GET /users, POST /users, PUT /users/{id}
    • 避免使用类似/getUserList的基于动作的URL
  2. 规范使用HTTP方法
    • GET:获取资源(安全、幂等)
    • POST:创建资源(非幂等)
    • PUT:替换整个资源(幂等)
    • PATCH:部分更新资源(非幂等)
    • DELETE:删除资源(幂等)
  3. 使用合适的状态码
    • 200 OK:成功的GET/PUT/PATCH请求
    • 201 Created:成功的POST请求,需返回Location标头
    • 204 No Content:成功的DELETE请求
    • 400 Bad Request:请求数据无效
    • 404 Not Found:资源不存在
    • 409 Conflict:资源重复
    • 500 Internal Server Error:意外错误
  4. 创建请求/响应DTO
    • 将API契约与领域实体分离
    • 使用Java记录(Record)或Lombok的
      @Data
      /
      @Value
      注解
    • 应用Jakarta校验注解
    • 尽可能保持DTO不可变
  5. 实现校验机制
    • @RequestBody
      参数上使用
      @Valid
      注解
    • 应用校验约束(
      @NotBlank
      ,
      @Email
      ,
      @Size
      等)
    • 通过
      MethodArgumentNotValidException
      处理校验错误
  6. 配置错误处理
    • 使用
      @RestControllerAdvice
      实现全局异常处理
    • 返回包含状态码、错误类型、消息及时间戳的标准化错误响应
    • 针对特定HTTP状态码使用
      ResponseStatusException
  7. 配置分页
    • 对大数据集使用Pageable
    • 包含page、size、sort参数
    • 返回包含总元素数、总页数等元数据
  8. 添加安全标头
    • 配置CORS策略
    • 设置内容安全策略
    • 包含X-Frame-Options、X-Content-Type-Options标头

Examples

示例

Basic CRUD Controller

基础CRUD控制器

java
@RestController
@RequestMapping("/v1/users")
@RequiredArgsConstructor
@Slf4j
public class UserController {
    private final UserService userService;

    @GetMapping
    public ResponseEntity<Page<UserResponse>> getAllUsers(
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "10") int pageSize) {
        log.debug("Fetching users page {} size {}", page, pageSize);
        Page<UserResponse> users = userService.getAll(page, pageSize);
        return ResponseEntity.ok(users);
    }

    @GetMapping("/{id}")
    public ResponseEntity<UserResponse> getUserById(@PathVariable Long id) {
        return ResponseEntity.ok(userService.getById(id));
    }

    @PostMapping
    public ResponseEntity<UserResponse> createUser(@Valid @RequestBody CreateUserRequest request) {
        UserResponse created = userService.create(request);
        return ResponseEntity.status(HttpStatus.CREATED).body(created);
    }

    @PutMapping("/{id}")
    public ResponseEntity<UserResponse> updateUser(
            @PathVariable Long id,
            @Valid @RequestBody UpdateUserRequest request) {
        return ResponseEntity.ok(userService.update(id, request));
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        userService.delete(id);
        return ResponseEntity.noContent().build();
    }
}
java
@RestController
@RequestMapping("/v1/users")
@RequiredArgsConstructor
@Slf4j
public class UserController {
    private final UserService userService;

    @GetMapping
    public ResponseEntity<Page<UserResponse>> getAllUsers(
            @RequestParam(defaultValue = "0") int page,
            @RequestParam(defaultValue = "10") int pageSize) {
        log.debug("Fetching users page {} size {}", page, pageSize);
        Page<UserResponse> users = userService.getAll(page, pageSize);
        return ResponseEntity.ok(users);
    }

    @GetMapping("/{id}")
    public ResponseEntity<UserResponse> getUserById(@PathVariable Long id) {
        return ResponseEntity.ok(userService.getById(id));
    }

    @PostMapping
    public ResponseEntity<UserResponse> createUser(@Valid @RequestBody CreateUserRequest request) {
        UserResponse created = userService.create(request);
        return ResponseEntity.status(HttpStatus.CREATED).body(created);
    }

    @PutMapping("/{id}")
    public ResponseEntity<UserResponse> updateUser(
            @PathVariable Long id,
            @Valid @RequestBody UpdateUserRequest request) {
        return ResponseEntity.ok(userService.update(id, request));
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        userService.delete(id);
        return ResponseEntity.noContent().build();
    }
}

Request/Response DTOs

请求/响应DTO

java
// Request DTO
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CreateUserRequest {
    @NotBlank(message = "User name cannot be blank")
    private String name;

    @Email(message = "Valid email required")
    private String email;
}

// Response DTO
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserResponse {
    private Long id;
    private String name;
    private String email;
    private LocalDateTime createdAt;
}
java
// Request DTO
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CreateUserRequest {
    @NotBlank(message = "User name cannot be blank")
    private String name;

    @Email(message = "Valid email required")
    private String email;
}

// Response DTO
@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserResponse {
    private Long id;
    private String name;
    private String email;
    private LocalDateTime createdAt;
}

Global Exception Handler

全局异常处理器

java
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleValidationException(
            MethodArgumentNotValidException ex, WebRequest request) {
        String errors = ex.getBindingResult().getFieldErrors().stream()
                .map(f -> f.getField() + ": " + f.getDefaultMessage())
                .collect(Collectors.joining(", "));

        ErrorResponse errorResponse = new ErrorResponse(
                HttpStatus.BAD_REQUEST.value(),
                "Validation Error",
                "Validation failed: " + errors,
                request.getDescription(false).replaceFirst("uri=", "")
        );
        return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
    }

    @ExceptionHandler(ResponseStatusException.class)
    public ResponseEntity<ErrorResponse> handleResponseStatusException(
            ResponseStatusException ex, WebRequest request) {
        ErrorResponse error = new ErrorResponse(
            ex.getStatusCode().value(),
            ex.getStatusCode().toString(),
            ex.getReason(),
            request.getDescription(false).replaceFirst("uri=", "")
        );
        return new ResponseEntity<>(error, ex.getStatusCode());
    }
}
java
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<ErrorResponse> handleValidationException(
            MethodArgumentNotValidException ex, WebRequest request) {
        String errors = ex.getBindingResult().getFieldErrors().stream()
                .map(f -> f.getField() + ": " + f.getDefaultMessage())
                .collect(Collectors.joining(", "));

        ErrorResponse errorResponse = new ErrorResponse(
                HttpStatus.BAD_REQUEST.value(),
                "Validation Error",
                "Validation failed: " + errors,
                request.getDescription(false).replaceFirst("uri=", "")
        );
        return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
    }

    @ExceptionHandler(ResponseStatusException.class)
    public ResponseEntity<ErrorResponse> handleResponseStatusException(
            ResponseStatusException ex, WebRequest request) {
        ErrorResponse error = new ErrorResponse(
            ex.getStatusCode().value(),
            ex.getStatusCode().toString(),
            ex.getReason(),
            request.getDescription(false).replaceFirst("uri=", "")
        );
        return new ResponseEntity<>(error, ex.getStatusCode());
    }
}

Best Practices

最佳实践

1. Use Constructor Injection

1. 使用构造函数注入

java
@Service
@RequiredArgsConstructor
public class UserService {
    private final UserRepository userRepository;
    private final EmailService emailService;
    // Dependencies are explicit and testable
}
java
@Service
@RequiredArgsConstructor
public class UserService {
    private final UserRepository userRepository;
    private final EmailService emailService;
    // 依赖关系明确且易于测试
}

2. Prefer Immutable DTOs

2. 优先使用不可变DTO

java
// Java records (JDK 16+)
public record UserResponse(Long id, String name, String email, LocalDateTime createdAt) {}

// Lombok @Value for immutability
@Value
public class UserResponse {
    Long id;
    String name;
    String email;
    LocalDateTime createdAt;
}
java
// Java记录(JDK 16+)
public record UserResponse(Long id, String name, String email, LocalDateTime createdAt) {}

// 使用Lombok @Value实现不可变性
@Value
public class UserResponse {
    Long id;
    String name;
    String email;
    LocalDateTime createdAt;
}

3. Validate Input Early

3. 尽早校验输入

java
@PostMapping
public ResponseEntity<UserResponse> createUser(@Valid @RequestBody CreateUserRequest request) {
    // Validation happens automatically before method execution
    return ResponseEntity.status(HttpStatus.CREATED).body(userService.create(request));
}
java
@PostMapping
public ResponseEntity<UserResponse> createUser(@Valid @RequestBody CreateUserRequest request) {
    // 方法执行前自动完成校验
    return ResponseEntity.status(HttpStatus.CREATED).body(userService.create(request));
}

4. Use ResponseEntity Flexibly

4. 灵活使用ResponseEntity

java
return ResponseEntity.status(HttpStatus.CREATED)
    .header("Location", "/api/users/" + created.getId())
    .header("X-Total-Count", String.valueOf(userService.count()))
    .body(created);
java
return ResponseEntity.status(HttpStatus.CREATED)
    .header("Location", "/api/users/" + created.getId())
    .header("X-Total-Count", String.valueOf(userService.count()))
    .body(created);

5. Implement Proper Transaction Management

5. 实现规范的事务管理

java
@Service
@Transactional
public class UserService {

    @Transactional(readOnly = true)
    public Optional<User> findById(Long id) {
        return userRepository.findById(id);
    }

    @Transactional
    public User create(User user) {
        return userRepository.save(user);
    }
}
java
@Service
@Transactional
public class UserService {

    @Transactional(readOnly = true)
    public Optional<User> findById(Long id) {
        return userRepository.findById(id);
    }

    @Transactional
    public User create(User user) {
        return userRepository.save(user);
    }
}

6. Add Meaningful Logging

6. 添加有意义的日志

java
@Slf4j
@Service
public class UserService {
    public User create(User user) {
        log.info("Creating user with email: {}", user.getEmail());
        return userRepository.save(user);
    }
}
java
@Slf4j
@Service
public class UserService {
    public User create(User user) {
        log.info("Creating user with email: {}", user.getEmail());
        return userRepository.save(user);
    }
}

7. Document APIs with Javadoc

7. 使用Javadoc文档化API

java
/**
 * Retrieves a user by id.
 *
 * @param id the user id
 * @return ResponseEntity containing a UserResponse
 * @throws ResponseStatusException with 404 if user not found
 */
@GetMapping("/{id}")
public ResponseEntity<UserResponse> getUserById(@PathVariable Long id)
java
/**
 * 根据ID获取用户信息。
 *
 * @param id 用户ID
 * @return 包含UserResponse的ResponseEntity
 * @throws ResponseStatusException 当用户不存在时返回404状态码
 */
@GetMapping("/{id}")
public ResponseEntity<UserResponse> getUserById(@PathVariable Long id)

Constraints and Warnings

约束与注意事项

1. Never Expose Entities Directly

1. 切勿直接暴露领域实体

Use DTOs to separate API contracts from domain models. This prevents accidental exposure of internal data structures and allows API evolution without database schema changes.
使用DTO分离API契约与领域模型。这可防止意外暴露内部数据结构,并允许API独立于数据库架构进行演进。

2. Follow REST Conventions Strictly

2. 严格遵循REST约定

  • Use nouns for resource names, not verbs
  • Use correct HTTP methods for operations
  • Use plural resource names (/users, not /user)
  • Return appropriate HTTP status codes for each operation
  • 资源名称使用名词而非动词
  • 为操作使用正确的HTTP方法
  • 资源名称使用复数形式(/users而非/user)
  • 为每个操作返回合适的HTTP状态码

3. Handle All Exceptions Globally

3. 全局处理所有异常

Use @RestControllerAdvice to catch all exceptions consistently. Don't let raw exceptions bubble up to clients.
使用@RestControllerAdvice统一捕获所有异常。切勿让原始异常直接返回给客户端。

4. Always Paginate Large Result Sets

4. 对大数据集始终实现分页

For GET endpoints that might return many results, implement pagination to prevent performance issues and DDoS vulnerabilities.
对于可能返回大量结果的GET端点,实现分页以避免性能问题与DDoS漏洞。

5. Validate All Input Data

5. 校验所有输入数据

Never trust client input. Use Jakarta validation annotations on all request DTOs to validate data at the controller boundary.
切勿信任客户端输入。在所有请求DTO上使用Jakarta校验注解,在控制器层完成数据校验。

6. Use Constructor Injection Exclusively

6. 仅使用构造函数注入

Avoid field injection (
@Autowired
) for better testability and explicit dependency declaration.
避免字段注入(
@Autowired
),以提升可测试性并明确依赖声明。

7. Keep Controllers Thin

7. 保持控制器轻量化

Controllers should only handle HTTP request/response adaptation. Delegate business logic to service layers.
控制器仅应处理HTTP请求/响应适配。将业务逻辑委托给服务层。

8. API Versioning

8. API版本化

Always version APIs from the start (e.g.,
/v1/users
) to allow future changes without breaking existing clients.
从项目初期就对API进行版本化(如
/v1/users
),以便未来进行变更时不影响现有客户端。

9. Sensitive Data Protection

9. 敏感数据保护

Never log or expose sensitive data (passwords, tokens, PII) in API responses or logs.
切勿在API响应或日志中记录或暴露敏感数据(密码、令牌、个人可识别信息等)。

References

参考资料

  • See
    references/
    directory for comprehensive reference material including HTTP status codes, Spring annotations, and detailed examples
  • Refer to the
    developer-kit-java:spring-boot-code-review-expert
    agent for code review guidelines
  • Review
    spring-boot-dependency-injection/SKILL.md
    for dependency injection patterns
  • Check
    ../spring-boot-test-patterns/SKILL.md
    for testing REST APIs
  • 查看
    references/
    目录获取全面参考资料,包括HTTP状态码、Spring注解及详细示例
  • 参考
    developer-kit-java:spring-boot-code-review-expert
    技能获取代码评审指南
  • 查看
    spring-boot-dependency-injection/SKILL.md
    获取依赖注入模式
  • 查阅
    ../spring-boot-test-patterns/SKILL.md
    获取REST API测试方法