Backend Development 26 min read

Comprehensive Guide to Spring Boot Backend API Design: Validation, Global Exception Handling, Unified Response, Version Control, and Security

This article presents a step‑by‑step tutorial on building robust Spring Boot backend APIs, covering environment setup, parameter validation techniques, global exception handling, unified response structures, optional response wrapping, API versioning via path or header, and comprehensive security measures such as token authentication, timestamp checks, request signing, replay protection, and HTTPS.

Java Architect Essentials
Java Architect Essentials
Java Architect Essentials
Comprehensive Guide to Spring Boot Backend API Design: Validation, Global Exception Handling, Unified Response, Version Control, and Security

1. Introduction

A backend API typically consists of four parts: URL, HTTP method, request data, and response data. While there is no universal standard, consistency and proper structuring are essential.

2. Environment Setup

Include the necessary dependencies such as spring-boot-starter-web , spring-boot-starter-validation , knife4j-spring-boot-starter , and Lombok in your pom.xml :

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-spring-boot-starter</artifactId>
    <version>2.0.2</version>
</dependency>

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

3. Parameter Validation

3.1 Introduction

Three common validation approaches are demonstrated, with the third (Validator + automatic exception) being the most concise.

3.2 Validator + BindingResult

Manually handle BindingResult to collect error messages:

@PostMapping("/addUser")
public String addUser(@RequestBody @Validated User user, BindingResult bindingResult) {
    List
allErrors = bindingResult.getAllErrors();
    if (!allErrors.isEmpty()) {
        return allErrors.stream()
                         .map(o -> o.getDefaultMessage())
                         .collect(Collectors.toList())
                         .toString();
    }
    return validationService.addUser(user);
}

3.3 Validator + Automatic Exception

Annotate request objects with constraints (e.g., @NotNull , @Size ) and add @Valid on the controller method. Validation failures automatically throw MethodArgumentNotValidException :

@Data
public class User {
    @NotNull(message = "用户id不能为空")
    private Long id;

    @NotNull(message = "用户账号不能为空")
    @Size(min = 6, max = 11, message = "账号长度必须是6-11个字符")
    private String account;

    @NotNull(message = "用户密码不能为空")
    @Size(min = 6, max = 16, message = "密码长度必须是6-16个字符")
    private String password;

    @NotNull(message = "用户邮箱不能为空")
    @Email(message = "邮箱格式不正确")
    private String email;
}

Controller method:

@RestController
@RequestMapping("user")
public class ValidationController {
    @Autowired
    private ValidationService validationService;

    @PostMapping("/addUser")
    public String addUser(@RequestBody @Validated User user) {
        return validationService.addUser(user);
    }
}

3.4 Group and Recursive Validation

Define a marker interface for a validation group, apply groups = Update.class on constraint annotations, and specify the group on the controller method with @Validated({Update.class}) . Recursive validation is achieved by adding @Valid on nested objects.

3.5 Custom Validation

Create a custom annotation and its validator:

@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
@Constraint(validatedBy = { HaveNoBlankValidator.class })
public @interface HaveNoBlank {
    String message() default "字符串中不能含有空格";
    Class
[] groups() default {};
    Class
[] payload() default {};
}

public class HaveNoBlankValidator implements ConstraintValidator
{
    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        if (value == null) return true;
        return !value.contains(" ");
    }
}

4. Global Exception Handling

Use @RestControllerAdvice (or @ControllerAdvice ) to catch validation and other exceptions and return a unified response:

@RestControllerAdvice
public class ExceptionControllerAdvice {
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public ResultVO
handleMethodArgumentNotValid(MethodArgumentNotValidException e) {
        List
messages = e.getBindingResult().getAllErrors()
                                 .stream()
                                 .map(DefaultMessageSourceResolvable::getDefaultMessage)
                                 .collect(Collectors.toList());
        return new ResultVO<>(ResultCode.VALIDATE_FAILED, messages.toString());
    }

    @ExceptionHandler(APIException.class)
    public ResultVO
handleAPIException(APIException e) {
        return new ResultVO<>(ResultCode.FAILED, e.getMsg());
    }

    @ExceptionHandler(Exception.class)
    public ResultVO
handleUnexpected(Exception ex) {
        log.error("系统异常:", ex);
        return new ResultVO<>(ResultCode.ERROR);
    }
}

5. Unified Response Structure

Define an enum for response codes and a generic wrapper class:

@Getter
public enum ResultCode {
    SUCCESS(1000, "操作成功"),
    FAILED(1001, "响应失败"),
    VALIDATE_FAILED(1002, "参数校验失败"),
    ERROR(5000, "未知错误");
    private final int code;
    private final String msg;
    ResultCode(int code, String msg) { this.code = code; this.msg = msg; }
}

@Getter
public class ResultVO
{
    private int code;
    private String msg;
    private T data;
    public ResultVO(T data) { this(ResultCode.SUCCESS, data); }
    public ResultVO(ResultCode rc, T data) {
        this.code = rc.getCode();
        this.msg = rc.getMsg();
        this.data = data;
    }
}

6. Optional Global Response Wrapping

Implement ResponseBodyAdvice to automatically wrap non‑ResultVO responses, with a custom @NotResponseBody annotation to opt‑out:

@Retention(RUNTIME)
@Target({ElementType.METHOD})
public @interface NotResponseBody {}

@RestControllerAdvice(basePackages = {"com.csdn.demo1.controller"})
public class ResponseControllerAdvice implements ResponseBodyAdvice
{
    @Override
    public boolean supports(MethodParameter returnType, Class
> converterType) {
        return !(returnType.getParameterType().equals(ResultVO.class) ||
                 returnType.hasMethodAnnotation(NotResponseBody.class));
    }
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
                                  Class
> selectedConverterType,
                                  ServerHttpRequest request, ServerHttpResponse response) {
        if (returnType.getGenericParameterType().equals(String.class)) {
            try {
                return new ObjectMapper().writeValueAsString(new ResultVO<>(body));
            } catch (JsonProcessingException e) {
                throw new APIException("返回String类型错误");
            }
        }
        return new ResultVO<>(body);
    }
}

7. API Version Control

7.1 Path‑Based Versioning

Create @ApiVersion annotation, a custom RequestCondition , and register a PathVersionHandlerMapping to route requests according to the version segment in the URL.

7.2 Header‑Based Versioning

Modify the condition to read a custom header (e.g., X-VERSION ) and match the requested version.

8. API Security

Implement a multi‑layer security strategy:

Token authentication stored in Redis with expiration.

Timestamp validation to reject stale requests.

Request signing (sorted parameters + secret key → MD5) to detect tampering.

Replay‑attack protection by caching signatures for the timestamp window.

Enforce HTTPS to encrypt traffic.

9. Conclusion

The tutorial demonstrates how to construct a clean, maintainable Spring Boot backend API by combining concise validation, centralized exception handling, unified response formats, optional response wrapping, version control, and robust security measures, allowing developers to focus on business logic.

JavaException HandlingvalidationSpring BootsecurityAPIVersioning
Java Architect Essentials
Written by

Java Architect Essentials

Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.