Refactoring Spring Boot Controllers: From Chaotic to Elegant with @Valid and Global Exception Handling
This article demonstrates how to transform overly complex Spring Boot controllers—filled with repetitive validation, try‑catch blocks, and business logic—into clean, maintainable implementations by applying @Valid annotations, streamlined validation, and a centralized global exception handler.
Preface
Have you ever encountered a controller with thousands of lines of code? I have. Have you seen a controller full of try‑catch blocks, field validations, or even business logic? I have, and it makes my blood pressure rise.
Main Content
Untidy Controller
@RestController
@RequestMapping("/user/test")
public class UserController {
private static Logger logger = LoggerFactory.getLogger(UserController.class);
@Autowired
private UserService userService;
@Autowired
private AuthService authService;
@PostMapping
public CommonResult userRegistration(@RequestBody UserVo userVo) {
if (StringUtils.isBlank(userVo.getUsername())) {
return CommonResult.error("用户名不能为空");
}
if (StringUtils.isBlank(userVo.getPassword())) {
return CommonResult.error("密码不能为空");
}
logger.info("注册用户:{}", userVo.getUsername());
try {
userService.registerUser(userVo.getUsername());
return CommonResult.ok();
} catch (Exception e) {
logger.error("注册用户失败:{}", userVo.getUsername(), e);
return CommonResult.error("注册失败");
}
}
@PostMapping("/login")
@PermitAll
@ApiOperation("使用账号密码登录")
public CommonResult
login(@RequestBody AuthLoginReqVO reqVO) {
if (StringUtils.isBlank(reqVO.getUsername())) {
return CommonResult.error("用户名不能为空");
}
if (StringUtils.isBlank(reqVO.getPassword())) {
return CommonResult.error("密码不能为空");
}
try {
return success(authService.login(reqVO));
} catch (Exception e) {
logger.error("注册用户失败:{}", reqVO.getUsername(), e);
return CommonResult.error("注册失败");
}
}
}Elegant Controller
@RestController
@RequestMapping("/user/test")
public class UserController1 {
private static Logger logger = LoggerFactory.getLogger(UserController1.class);
@Autowired
private UserService userService;
@Autowired
private AuthService authService;
@PostMapping("/userRegistration")
public CommonResult userRegistration(@RequestBody @Valid UserVo userVo) {
userService.registerUser(userVo.getUsername());
return CommonResult.ok();
}
@PostMapping("/login")
@PermitAll
@ApiOperation("使用账号密码登录")
public CommonResult
login(@RequestBody @Valid AuthLoginReqVO reqVO) {
return success(authService.login(reqVO));
}
}With this refactor the code size is cut roughly in half, not counting the business logic that was previously embedded directly in the controller.
Refactoring Process
Validation Approach
The repetitive if‑checks are replaced by assertions such as Assert.notNull(userVo.getUsername(), "用户名不能为空") or, better yet, by using Spring's @Valid annotation.
Apply @Valid on method parameters and annotate the corresponding VO classes with validation constraints (e.g., @NotEmpty , @Length , @Pattern ).
@ApiModel(value = "管理后台 - 账号密码登录 Request VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class AuthLoginReqVO {
@ApiModelProperty(value = "账号", required = true, example = "user")
@NotEmpty(message = "登录账号不能为空")
@Length(min = 4, max = 16, message = "账号长度为 4-16 位")
@Pattern(regexp = "^[A-Za-z0-9]+$", message = "账号格式为数字以及字母")
private String username;
@ApiModelProperty(value = "密码", required = true, example = "password")
@NotEmpty(message = "密码不能为空")
@Length(min = 4, max = 16, message = "密码长度为 4-16 位")
private String password;
}@Valid
In Spring Boot, @Valid is a powerful annotation for data validation. It can be placed on entity fields to trigger constraint checks, and on controller method parameters to activate validation for incoming request bodies.
Why use @Valid for parameter validation : It eliminates verbose if‑else checks, improving readability.
Function of @Valid : Validates annotated entity properties according to the constraints defined on them.
Related annotations : Use annotations like @NotEmpty , @Length , @Pattern on fields.
Validation steps with @Valid : The request reaches the controller, Spring validates the object, and if validation fails, an exception is thrown and can be handled globally.
@Validated vs @Valid : @Validated is a variant of @Valid that supports validation groups to selectively apply constraints.
Global Exception Handling
A centralized exception handler can capture validation errors and other runtime exceptions, providing consistent error responses.
@ResponseBody
@RestControllerAdvice
public class ExceptionHandlerAdvice {
protected Logger logger = LoggerFactory.getLogger(getClass());
@ExceptionHandler(MethodArgumentNotValidException.class)
public CommonResult
handleValidationExceptions(MethodArgumentNotValidException ex) {
logger.error("[handleValidationExceptions]", ex);
StringBuilder sb = new StringBuilder();
ex.getBindingResult().getAllErrors().forEach(error -> {
String fieldName = ((org.springframework.validation.FieldError) error).getField();
String errorMessage = error.getDefaultMessage();
sb.append(fieldName).append(":").append(errorMessage).append(";");
});
return CommonResult.error(sb.toString());
}
/**
* Handles all other uncaught exceptions as a fallback.
*/
@ExceptionHandler(value = Exception.class)
public CommonResult
defaultExceptionHandler(Throwable ex) {
logger.error("[defaultExceptionHandler]", ex);
return CommonResult.error(INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMsg());
}
}With these changes, you obtain a clean and maintainable controller architecture.
Other Blood‑Pressure‑Raising Moments in Daily Development
Images and anecdotes illustrate additional pain points such as overly long methods, duplicated logic across modules, and tangled service calls.
Summary
Embedding business logic in controllers leads to confusing, hard‑to‑maintain code.
Separate concerns: validation, business processing, and messaging should reside in dedicated layers.
Use @Valid and validation annotations to replace repetitive if‑checks.
Leverage a global exception handler to centralize error handling.
Keep controller methods focused on orchestrating calls and returning results.
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.