最后更新:2026-10-12
适用场景:Spring Boot 3.x、Spring MVC、JSON 请求体、@Valid、jakarta.validation
注册接口明明给 username 加了 @NotBlank,前端传空字符串,Controller 却照常执行。这个问题很容易让人先去改全局异常处理,实际上要先弄清楚:校验有没有被触发,以及触发后错误被谁接走了。这两件事不是一回事。
下面用一个很小的接口复现。示例基于 Java 17、Spring Boot 3.x,假设项目已经有 spring-boot-starter-web。
先让校验真正跑起来
pom.xml 中加入校验 Starter:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
使用 Spring Boot 的依赖管理时,不要给它单独指定版本。只有注解 API、没有校验实现的项目,代码可能写得出来,却不一定能按预期执行校验。Spring Boot 3.5 的校验文档也明确提到了这个 Starter。
建一个请求 DTO,注意导入的是 jakarta.validation:
import jakarta.validation.Valid;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
public record CreateUserRequest(
@NotBlank(message = "用户名不能为空") String username,
@NotNull(message = "年龄不能为空")
@Min(value = 1, message = "年龄不能小于 1") Integer age,
@NotNull(message = "地址不能为空")
@Valid Address address
) {
public record Address(
@NotBlank(message = "城市不能为空") String city
) {}
}
Controller 先只做一件事:确认请求能否走到方法体。
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ValidationDemoController {
@PostMapping("/validation-demo")
public String check(@Valid @RequestBody CreateUserRequest request) {
return "校验通过";
}
}
在 Postman 或 Apifox 中,以 Content-Type: application/json 发送:
{
"username": "",
"age": 18,
"address": { "city": "杭州" }
}
正常情况下,请求会在进入 check 方法体之前被拦下,默认是 HTTP 400;成功路径才会返回“校验通过”。先别纠结错误响应长什么样,项目里若有自定义异常处理,响应体可能不同。判断校验是否生效,先看状态码,再看方法体有没有执行。
还返回成功?按请求经过的地方查
第一处:依赖。 看 pom.xml 或 Gradle 配置里是否真的有 spring-boot-starter-validation,并确认依赖已重新加载。如果是多模块项目,还要看运行的那个模块能否拿到校验实现,而不只是父模块声明过它。
第二处:Controller 参数。 DTO 字段上有约束,不代表请求会自动校验。接 JSON 对象时,方法参数应是 @Valid @RequestBody CreateUserRequest request。少了 @Valid,对象能反序列化,字段约束却不会在这条请求路径上按示例触发;少了 @RequestBody,则不是按这段 JSON 请求体绑定的写法了。@Valid 应放在 Controller 接收的对象参数上,不是只放在 DTO 类名上。
第三处:注解是否约束了你真正想拦的值。 @NotBlank 适合字符串,空串、纯空格都过不了。年龄是 Integer,要用 @NotNull 拦缺失值,再用 @Min(1) 拦过小的值。只写 @Min(1) 并不能表达“这个字段必须提交”。这些约束的定义可在 Jakarta Bean Validation 规范核对。
第四处:嵌套对象。 CreateUserRequest.address.city 上虽然有 @NotBlank,但外层 address 没写 @Valid 时,不会继续向内校验 city。反过来,仅写 @Valid 也不能保证 address 本身非空,所以示例同时用了 @NotNull 和 @Valid。把上面的请求改成 "city": "",正好能单独验证这一点。
如果项目从 Spring Boot 2 升到 3,还要检查是否混用了旧的 javax.validation 导入。上面的 Boot 3 示例使用 jakarta.validation;不要在同一个 DTO 和 Controller 示例里把两套包名混着写。
请求没到校验这一步,也会返回 400
把 JSON 故意写坏,例如少一个右花括号,请求也可能返回 400,但那是 JSON 解析失败,不是 @NotBlank 起作用。字段类型对不上同理:给 age 传一个无法转换成整数的字符串,通常会先卡在请求体读取阶段。
排查时用语法正确、字段类型正确、只违反一条约束的请求。比如上面的空 username。这样日志里的异常和响应状态才容易对应。JSON 接收本身有问题,可以先看 Spring Boot 接收 JSON 参数。
校验生效了,为什么全局异常处理没收到?
对这个 @Valid @RequestBody 对象参数,Spring MVC 通常抛出 MethodArgumentNotValidException。但下面这种签名是另一种用法:
public String check(@Valid @RequestBody CreateUserRequest request,
BindingResult bindingResult) {
// 这里需要自行检查 bindingResult.hasErrors() 并决定如何返回
}
BindingResult 紧跟在被校验参数后面时,方法可以自行处理校验错误。此时不要再等全局 @ExceptionHandler(MethodArgumentNotValidException.class) 来替你返回错误。如果原来的接口加过 BindingResult,却从未检查 hasErrors(),就会出现“明明校验失败,业务代码仍继续跑”的现象。
还有一种容易混淆的情况:约束直接写在 Controller 方法参数上,例如 @RequestParam @Min(1) int page。在 Spring Framework 6.1 及之后的 MVC 方法校验中,这类错误可能是 HandlerMethodValidationException,不是前面的 MethodArgumentNotValidException。如果 Controller 类上还留着 @Validated,也要检查它是否让方法校验走了 AOP 路径。两种异常和类级 @Validated 的区别,见 Spring Framework 6.2 的 MVC 校验文档。具体怎么把错误变成统一 JSON,前一篇 Spring Boot 全局异常处理怎么写已经给了实现,这里不重复贴一套 Advice。
三个请求测完再改代码
| 请求体变化 | 预期结果 | 主要验证什么 |
|---|---|---|
username 为 "张三",其余字段合法 | 进入方法体 | 成功路径正常 |
username 为 "",其余字段合法 | 校验失败,通常是 400 | 顶层 @Valid、@NotBlank |
address.city 为 "",其余字段合法 | 校验失败,通常是 400 | 嵌套 @Valid 是否生效 |
如果第二个请求仍然成功,优先回看 Starter、Controller 参数注解和导入包名;如果只有第三个成功,重点看 address 上的 @Valid。若接口已经返回 400,只是前端拿不到约定的 code、message,那是错误响应格式的问题,不是校验没生效。先把这两类问题分开,排查会快很多。
相关文章
Spring Boot 全局异常处理怎么写
Spring Boot 接收 JSON 参数
Spring Boot 统一返回结果怎么写
SpringBoot 教程
参考资料
Spring Boot 3.5:Validation
Spring Framework 6.2:MVC Validation
Jakarta Bean Validation 3.0 规范
更新记录
2026-10-12:创建文章,补充请求体、嵌套对象和 BindingResult 的排查示例。
Spring Boot 参数校验不生效怎么排查
https://java.li/archives/spring-boot-validation-not-working
评论