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.
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.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.