How to Turn Bloated Spring Controllers into Clean, Maintainable Code
The article analyzes why Spring MVC controllers often become massive with repetitive validation, logging, and business logic, demonstrates an ugly controller example, then shows step‑by‑step refactoring using @Valid, assertion utilities, and a global exception handler to dramatically reduce code size and improve readability.
Problem: many Spring MVC controllers become huge, mixing request mapping, validation, logging, and business logic, leading to unreadable code and high maintenance cost.
Ugly controller example
@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<AuthLoginRespVO> 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("注册失败");
}
}
}The author points out that the controller contains repetitive null checks, try‑catch blocks, and direct service calls, resulting in thousands of lines of code.
Refactored, 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<AuthLoginRespVO> login(@RequestBody @Valid AuthLoginReqVO reqVO) {
return success(authService.login(reqVO));
}
}By moving validation to @Valid and using assertion utilities, the method bodies shrink dramatically, roughly halving the line count.
Validation improvement
Replace manual if checks with Spring’s validation annotations. For example:
public CommonResult userRegistration(@RequestBody @Valid UserVo userVo) { … }Define constraints in the VO class using annotations such as @NotEmpty, @Length, and @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;
}Global exception handling
Implement a @RestControllerAdvice to catch MethodArgumentNotValidException and generic Exception, converting them into a unified CommonResult response.
@RestControllerAdvice
public class ExceptionHandlerAdvice {
protected Logger logger = LoggerFactory.getLogger(getClass());
@ExceptionHandler(MethodArgumentNotValidException.class)
public CommonResult<Object> 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());
}
/**
* Handle any uncaught exceptions as a fallback.
*/
@ExceptionHandler(Exception.class)
public CommonResult<?> defaultExceptionHandler(Throwable ex) {
logger.error("[defaultExceptionHandler]", ex);
return CommonResult.error(INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMsg());
}
}These steps produce a cleaner, more maintainable controller layer, aligning with best‑practice guidelines such as limiting method length to 80 lines.
Java Web Project
Focused on Java backend technologies, trending internet tech, and the latest industry developments. The platform serves over 200,000 Java developers, inviting you to learn and exchange ideas together. Check the menu for Java learning resources.
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.
