Backend Development 11 min read

Improving Spring Boot Controllers: From Bad Practices to Clean Design with @Valid and Global Exception Handling

This article examines common pitfalls in Spring Boot controller implementations, demonstrates how to refactor messy controllers into clean, maintainable code using @Valid for input validation and a centralized exception handler, and highlights best‑practice guidelines for backend development.

Top Architect
Top Architect
Top Architect
Improving Spring Boot Controllers: From Bad Practices to Clean Design with @Valid and Global Exception Handling

The author shares frustrations with overly complex Spring MVC controllers that mix validation, business logic, and error handling, and then presents a step‑by‑step refactor to a cleaner architecture.

Unclean 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("注册失败");
        }
    }
}

Clean 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));
    }
}

By moving validation to @Valid annotations and delegating business logic to service layers, the controller size is roughly halved and becomes far more readable.

Validation Approach

Replace repetitive if checks with Spring's validation framework, e.g., Assert.notNull(userVo.getUsername(), "用户名不能为空") , or annotate request DTOs with @Valid and constraint annotations.

@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 Overview

@Valid is a powerful Spring annotation for declarative data validation. It can be placed on method parameters or on fields within a DTO, triggering validation based on the constraint annotations present. When validation fails, Spring throws a MethodArgumentNotValidException , which can be handled globally.

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());
    }
}

The article concludes that keeping controllers thin, delegating validation and business logic, and using a centralized exception handler dramatically improves code maintainability and reduces the risk of bugs.

Note: The original post also contains promotional messages for paid AI services and community groups, which are not part of the technical tutorial.

JavaBackend Developmentexception handlingvalidationSpring BootController Design
Top Architect
Written by

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.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.