Unified Parameter Handling, Validation, and Response Wrapping in Spring Boot Controllers
This article explains how to structure Spring Boot controller methods to receive parameters, implement unified status codes, validation with @Validated, and automatic response wrapping using ResultVo and @RestControllerAdvice, while also providing custom exception handling and optional exclusion annotations for flexible API design.
The article introduces a systematic approach for handling controller layer logic in Spring Boot applications, covering four main aspects: parameter reception, unified status codes, validation, and response wrapping.
Controller Parameter Reception
Typical controller methods are defined with @RestController and mapping annotations such as @GetMapping and @PostMapping . Example:
@RestController
@RequestMapping("/product/product-info")
public class ProductInfoController {
@Autowired
private ProductInfoService productInfoService;
@GetMapping("/findById")
public ProductInfoQueryVo findById(Integer id) { ... }
@PostMapping("/page")
public IPage findPage(Page page, ProductInfoQueryVo vo) { ... }
}Unified Status Codes
A StatusCode interface defines getCode() and getMsg() . An enum ResultCode implements this interface, providing constants such as SUCCESS(1000, "请求成功") , FAILED(1001, "请求失败") , and VALIDATE_ERROR(1002, "参数校验失败") .
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 final int code;
private final String msg;
ResultCode(int code, String msg) { this.code = code; this.msg = msg; }
}Unified Response Wrapper
The ResultVo class encapsulates code , msg , and data . Controllers can simply return the data object, and the wrapper will be added automatically.
@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 status, Object data) {
this.code = status.getCode();
this.msg = status.getMsg();
this.data = data;
}
}Parameter Validation
Using Bean Validation annotations together with @Validated on method parameters eliminates manual checks. Example VO:
@Data
public class ProductInfoVo {
@NotNull(message = "商品名称不允许为空")
private String productName;
@Min(value = 0, message = "商品价格不允许为负数")
private BigDecimal productPrice;
private Integer productStatus;
}When validation fails, Spring throws BindException , which is intercepted globally.
Global Exception Handling
A @RestControllerAdvice class captures validation errors and custom business exceptions, converting them to ResultVo responses.
@RestControllerAdvice
public class ControllerExceptionAdvice {
@ExceptionHandler(BindException.class)
public ResultVo handleBindException(BindException e) {
ObjectError err = e.getBindingResult().getAllErrors().get(0);
return new ResultVo(ResultCode.VALIDATE_ERROR, err.getDefaultMessage());
}
@ExceptionHandler(APIException.class)
public ResultVo handleAPIException(APIException e) {
// log.error(e.getMessage(), e);
return new ResultVo(e.getCode(), e.getMsg(), e.getMessage());
}
}Automatic Response Wrapping with ResponseBodyAdvice
The ControllerResponseAdvice implements ResponseBodyAdvice<Object> to wrap any non‑ ResultVo response. It also skips wrapping for String return types by converting the wrapped object to JSON.
@RestControllerAdvice(basePackages = {"com.example"})
public class ControllerResponseAdvice implements ResponseBodyAdvice
{
@Override
public boolean supports(MethodParameter returnType, Class
> converterType) {
return !ResultVo.class.isAssignableFrom(returnType.getParameterType()) &&
!returnType.hasMethodAnnotation(NotControllerResponseAdvice.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(ResultCode.RESPONSE_PACK_ERROR, e.getMessage());
}
}
return new ResultVo(body);
}
}Optional Exclusion Annotation
For endpoints that must return raw data (e.g., health checks), a custom @NotControllerResponseAdvice annotation can be placed on the method to bypass the wrapper.
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface NotControllerResponseAdvice {}
@RestController
public class HealthController {
@GetMapping("/health")
@NotControllerResponseAdvice
public String health() { return "success"; }
}With these components, developers can write clean controller methods that return domain objects directly, while the framework ensures consistent API contracts, proper validation feedback, and centralized error logging.
Top Architect
Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn 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.