How to Refactor Bloated Spring Controllers into Clean, Maintainable Code
The article examines common pain points of oversized Spring controllers, contrasts a verbose implementation with a concise version that leverages @Valid and assertion utilities, and provides step‑by‑step guidance on validation annotations, global exception handling, and best‑practice refactoring to improve readability and maintainability.
In many Java projects developers encounter controllers that span thousands of lines, contain repetitive if checks, extensive try‑catch blocks, and embed business logic directly in the controller layer, which dramatically raises the “blood pressure” of anyone reading the code.
Problem illustration: an overly verbose 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<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("注册失败");
}
}
}This version mixes validation, logging, and business calls, resulting in duplicated checks and a controller that is roughly twice as long as necessary.
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 delegating validation to Spring’s @Valid mechanism, the controller shrinks dramatically—roughly half the lines—while keeping the same functional behavior.
Refactoring process
Replace manual if checks with assertions or validation annotations. For example, instead of writing:
if (StringUtils.isBlank(userVo.getUsername())) {
return CommonResult.error("用户名不能为空");
}use Spring’s assertion utility:
Assert.notNull(userVo.getUsername(), "用户名不能为空");Even better, annotate the request object with @Valid and let Spring perform the checks automatically.
Using @Valid in Spring Boot
Define a request VO with validation constraints:
@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;
}When the controller method receives @RequestBody @Valid AuthLoginReqVO reqVO, Spring validates each field according to the annotations. If any constraint fails, a MethodArgumentNotValidException is thrown.
Why prefer @Valid over manual checks
Reduces boilerplate code and duplication.
Centralises validation rules next to the data model.
Improves readability; the controller focuses solely on delegating to services.
Works seamlessly with Spring’s global exception handling.
Global exception handling for validation errors
@ResponseBody
@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());
}
@ExceptionHandler(value = Exception.class)
public CommonResult<?> defaultExceptionHandler(Throwable ex) {
logger.error("[defaultExceptionHandler]", ex);
return CommonResult.error(INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMsg());
}
}The first handler extracts each field‑level error from the binding result and concatenates them into a readable message. The second handler acts as a catch‑all, returning a generic error response for unexpected exceptions.
Takeaways
Embedding business logic directly in controllers leads to massive, hard‑to‑maintain classes. By moving validation to @Valid, using assertion utilities, and centralising error handling with @RestControllerAdvice, developers can keep controllers thin, improve code readability, and adhere to guidelines such as Alibaba’s recommendation that a method should not exceed 80 lines. This disciplined approach also makes unit testing easier because validation and business concerns are clearly separated.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Architect
Professional architect sharing high‑quality architecture insights. Topics include high‑availability, high‑performance, high‑stability architectures, big data, machine learning, Java, system and distributed architecture, AI, and practical large‑scale architecture case studies. Open to ideas‑driven architects who enjoy sharing and learning.
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.
