Unified Parameter Validation, Response Wrapping, and Exception Handling in Spring Boot Controllers
This article explains how to handle controller parameters, implement unified status codes with ResultVo, apply @Validated for request validation, and use @RestControllerAdvice together with ResponseBodyAdvice to automatically wrap responses and centralize exception handling in a Spring Boot backend.
The article introduces the four parts of a complete backend request—URL, HTTP method, request data, and response data—and outlines three problems: elegant parameter validation, unified response formatting, and exception handling.
1. Controller Parameter Reception
Shows basic @RestController, @RequestMapping, @GetMapping and @PostMapping usage with a sample ProductInfoController and explains how request bodies are automatically mapped to VO objects.
@RestController
@RequestMapping("/product/product-info")
public class ProductInfoController {
@Autowired
ProductInfoService productInfoService;
@GetMapping("/findById")
public ProductInfoQueryVo findById(Integer id) { ... }
@PostMapping("/page")
public IPage findPage(Page page, ProductInfoQueryVo vo) { ... }
}2. Unified Status Code
Defines a ResultVo wrapper that adds a numeric code , a message msg , and a data field to every response. The article recommends using an enum implementing a StatusCode interface instead of hard‑coding numbers.
public interface StatusCode {
int getCode();
String getMsg();
} @Getter
public enum ResultCode implements StatusCode {
SUCCESS(1000, "请求成功"),
FAILED(1001, "请求失败"),
VALIDATE_ERROR(1002, "参数校验失败"),
RESPONSE_PACK_ERROR(1003, "response返回包装失败");
private int code;
private String msg;
ResultCode(int code, String msg) { this.code = code; this.msg = msg; }
} @Data
public class ResultVo {
private int code;
private String msg;
private Object data;
public ResultVo(Object data) { this.code = ResultCode.SUCCESS.getCode(); this.msg = ResultCode.SUCCESS.getMsg(); this.data = data; }
public ResultVo(StatusCode statusCode, Object data) { this.code = statusCode.getCode(); this.msg = statusCode.getMsg(); this.data = data; }
public ResultVo(StatusCode statusCode) { this.code = statusCode.getCode(); this.msg = statusCode.getMsg(); this.data = null; }
}3. Unified Validation
Uses Bean Validation annotations (@NotNull, @Min) on VO fields and the @Validated annotation on controller methods. When validation fails, Spring throws BindException.
public class ProductInfoVo {
@NotNull(message = "商品名称不允许为空")
private String productName;
@Min(value = 0, message = "商品价格不允许为负数")
private BigDecimal productPrice;
private Integer productStatus;
}Example of a failed validation response (400 Bad Request) is shown.
4. Centralized Exception Handling
A @RestControllerAdvice class catches BindException and wraps it into a ResultVo with ResultCode.VALIDATE_ERROR . It also catches custom APIException to return its code, msg, and detailed message.
@RestControllerAdvice
public class ControllerExceptionAdvice {
@ExceptionHandler(BindException.class)
public ResultVo handleBindException(BindException e) {
ObjectError error = e.getBindingResult().getAllErrors().get(0);
return new ResultVo(ResultCode.VALIDATE_ERROR, error.getDefaultMessage());
}
@ExceptionHandler(APIException.class)
public ResultVo handleAPIException(APIException e) {
return new ResultVo(e.getCode(), e.getMsg(), e.getMessage());
}
}5. Unified Response Advice
Implements ResponseBodyAdvice to automatically wrap any non‑ResultVo response into ResultVo, except for String returns (which are converted to JSON) and methods annotated with a custom @NotControllerResponseAdvice.
@RestControllerAdvice(basePackages = {"com.bugpool.leilema"})
public class ControllerResponseAdvice implements ResponseBodyAdvice
{
@Override
public boolean supports(MethodParameter methodParameter, Class
> aClass) {
return !(methodParameter.getParameterType().isAssignableFrom(ResultVo.class)
|| methodParameter.hasMethodAnnotation(NotControllerResponseAdvice.class));
}
@Override
public Object beforeBodyWrite(Object data, MethodParameter returnType, MediaType mediaType,
Class
> aClass,
ServerHttpRequest request, ServerHttpResponse response) {
if (returnType.getGenericParameterType().equals(String.class)) {
ObjectMapper mapper = new ObjectMapper();
try { return mapper.writeValueAsString(new ResultVo(data)); }
catch (JsonProcessingException e) { throw new APIException(ResultCode.RESPONSE_PACK_ERROR, e.getMessage()); }
}
return new ResultVo(data);
}
}A custom annotation @NotControllerResponseAdvice can be placed on methods (e.g., health check) to skip the automatic wrapping.
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NotControllerResponseAdvice {}6. Business Exception Standardization
Defines AppCode enum for business errors, APIException class that carries a status code and message, and adds handling in ControllerExceptionAdvice to return a consistent JSON structure.
public enum AppCode implements StatusCode {
APP_ERROR(2000, "业务异常"),
PRICE_ERROR(2001, "价格异常");
private int code; private String msg; /* constructor omitted */
} @Getter
public class APIException extends RuntimeException {
private int code; private String msg;
public APIException(StatusCode statusCode, String message) { super(message); this.code = statusCode.getCode(); this.msg = statusCode.getMsg(); }
public APIException(String message) { super(message); this.code = AppCode.APP_ERROR.getCode(); this.msg = AppCode.APP_ERROR.getMsg(); }
}Finally, throwing new APIException(AppCode.ORDER_NOT_EXIST, "订单号不存在:" + orderId) results in a JSON response with code 2003, msg "订单不存在", and detailed data.
{ "code":2003, "msg":"订单不存在", "data":"订单号不存在:1998" }The article concludes that with these patterns developers can write clean controller code, let the framework handle validation and error formatting, and keep front‑end contracts stable.
Selected Java Interview Questions
A professional Java tech channel sharing common knowledge to help developers fill gaps. Follow us!
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.