Improving Spring Boot Controllers: From Messy to Elegant with @Valid and Global Exception Handling
This article examines common pitfalls in Spring Boot controller implementations—such as excessive try‑catch blocks, inline validation, and business logic—then demonstrates cleaner alternatives using @Valid, validation annotations, and global exception handling to produce concise, maintainable controllers.
The author shares frustration with poorly designed Spring Boot controllers that contain excessive try‑catch blocks, field validation, and business logic directly in the controller layer.
Un‑elegant 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));
}
}Refactoring steps include replacing manual if‑checks with assertions (e.g., Assert.notNull(userVo.getUsername(), "用户名不能为空") ) and leveraging Spring’s @Valid annotation on request DTOs to delegate validation to the framework.
Using @Valid allows developers to annotate entity fields with constraints such as @NotEmpty , @Length , and @Pattern . When a controller method receives a DTO annotated with @Valid , Spring automatically validates the fields and throws a MethodArgumentNotValidException if any constraint is violated.
The article also clarifies the difference between @Validated and @Valid : @Validated is a variant of @Valid that supports validation groups, enabling selective validation of fields.
Global exception handling
@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());
}
@ExceptionHandler(value = Exception.class)
public CommonResult
defaultExceptionHandler(Throwable ex) {
logger.error("[defaultExceptionHandler]", ex);
return CommonResult.error(INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMsg());
}
}In conclusion, keeping controllers thin, limiting method length (e.g., under 80 lines as recommended by Alibaba’s development handbook), and delegating validation and business logic to services and global handlers greatly improves code readability, maintainability, and reduces the risk of bugs.
Architect's Tech Stack
Java backend, microservices, distributed systems, containerized programming, and more.
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.