Building a Robust Backend API with Spring Boot: Validation, Global Exception Handling, and Unified Response
This article demonstrates how to build a clean Spring Boot backend API by adding the web starter, using Hibernate Validator for parameter checks, handling validation errors with global exception handling, defining custom exceptions, and standardizing responses with a unified ResultVO and response‑code enum.
Introduction
A backend interface typically consists of URL, HTTP method, request data, and response data. Standards vary across companies, but a well‑structured interface is essential for maintainability.
Required Dependencies
Only the spring-boot-starter-web dependency is required for the examples.
org.springframework.boot
spring-boot-starter-webSwagger and Lombok are optional.
Parameter Validation
Business‑layer validation
public String addUser(User user) {
if (user == null || user.getId() == null || user.getAccount() == null || user.getPassword() == null || user.getEmail() == null) {
return "对象或者对象字段不能为空";
}
if (StringUtils.isEmpty(user.getAccount()) || StringUtils.isEmpty(user.getPassword()) || StringUtils.isEmpty(user.getEmail())) {
return "不能输入空字符串";
}
if (user.getAccount().length() < 6 || user.getAccount().length() > 11) {
return "账号长度必须是6-11个字符";
}
if (user.getPassword().length() < 6 || user.getPassword().length() > 16) {
return "密码长度必须是6-16个字符";
}
if (!Pattern.matches("^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\\\.[a-zA-Z0-9_-]+)+$", user.getEmail())) {
return "邮箱格式不正确";
}
// 参数校验完毕后这里就写上业务逻辑
return "success";
}This approach works but is verbose.
Validator + BindingResult
Define validation annotations on the entity:
@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 with @Valid and BindingResult :
@RestController
@RequestMapping("user")
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/addUser")
public String addUser(@RequestBody @Valid User user, BindingResult bindingResult) {
// 如果有参数校验失败,会将错误信息封装成对象组装在BindingResult里
for (ObjectError error : bindingResult.getAllErrors()) {
return error.getDefaultMessage();
}
return userService.addUser(user);
}
}Spring automatically validates the request and populates BindingResult . If validation fails, the method returns the first error message.
Validator + Automatic Exception
Remove BindingResult and let Spring throw MethodArgumentNotValidException on validation failure:
@PostMapping("/addUser")
public String addUser(@RequestBody @Valid User user) {
return userService.addUser(user);
}The exception can be handled globally.
Global Exception Handling
Create a class annotated with @RestControllerAdvice and define handlers:
@RestControllerAdvice
public class ExceptionControllerAdvice {
@ExceptionHandler(MethodArgumentNotValidException.class)
public String MethodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
// 从异常对象中拿到ObjectError对象
ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
// 然后提取错误提示信息进行返回
return objectError.getDefaultMessage();
}
}This returns a clean error message without additional boilerplate.
Custom Exception
@Getter //只要getter方法,无需setter
public class APIException extends RuntimeException {
private int code;
private String msg;
public APIException() {
this(1001, "接口错误");
}
public APIException(String msg) {
this(1001, msg);
}
public APIException(int code, String msg) {
super(msg);
this.code = code;
this.msg = msg;
}
}Handle it in the advice:
@ExceptionHandler(APIException.class)
public String APIExceptionHandler(APIException e) {
return e.getMsg();
}Unified Response Structure
Define a generic ResultVO class:
@Getter
public class ResultVO
{
/** 状态码,比如1000代表响应成功 */
private int code;
/** 响应信息,用来说明响应情况 */
private String msg;
/** 响应的具体数据 */
private T data;
public ResultVO(T data) {
this(1000, "success", data);
}
public ResultVO(int code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
}Update exception handlers to return ResultVO with a response‑code enum.
Response Code Enum
@Getter
public enum ResultCode {
SUCCESS(1000, "操作成功"),
FAILED(1001, "响应失败"),
VALIDATE_FAILED(1002, "参数校验失败"),
ERROR(5000, "未知错误");
private int code;
private String msg;
ResultCode(int code, String msg) {
this.code = code;
this.msg = msg;
}
}Modify ResultVO to accept ResultCode :
public ResultVO(T data) {
this(ResultCode.SUCCESS, data);
}
public ResultVO(ResultCode resultCode, T data) {
this.code = resultCode.getCode();
this.msg = resultCode.getMsg();
this.data = data;
}Global Response Wrapper
@RestControllerAdvice(basePackages = {"com.rudecrab.demo.controller"})
public class ResponseControllerAdvice implements ResponseBodyAdvice
{
@Override
public boolean supports(MethodParameter returnType, Class
> aClass) {
// If the controller already returns ResultVO, skip wrapping
return !returnType.getGenericParameterType().equals(ResultVO.class);
}
@Override
public Object beforeBodyWrite(Object data, MethodParameter returnType, MediaType mediaType,
Class
> aClass,
ServerHttpRequest request, ServerHttpResponse response) {
// Special handling for String return type
if (returnType.getGenericParameterType().equals(String.class)) {
ObjectMapper objectMapper = new ObjectMapper();
try {
return objectMapper.writeValueAsString(new ResultVO<>(data));
} catch (JsonProcessingException e) {
throw new APIException("返回String类型错误");
}
}
// Wrap other types in ResultVO
return new ResultVO<>(data);
}
}With this advice, any controller method can return plain objects (or strings) and the response will be automatically wrapped in the unified format.
Conclusion
Validator + automatic exception provides concise parameter validation.
Global exception handling together with custom exceptions standardizes error processing.
Unified response objects and response‑code enums ensure consistent API contracts.
The combination reduces boilerplate and lets developers focus on business logic.
GitHub repository: https://github.com/RudeCrab/rude-java/tree/master/project-practice/validation-and-exception-handler
Java Captain
Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.
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.