前言:
1、 常用做法
Controller 层参数类型直接用 Java 类型(Integer、Long 等) → Spring 自带类型转换器会拦截非数字字符串。
加上 JSR-303 参数校验 (
@Valid
+@Min
等)→ 保证规则正确。数据库字段用合适类型(避免用 VARCHAR 存数字)。
这样,即使前端传错类型,错误会:
第一道防线:Spring 类型绑定失败 → 400 错误。
第二道防线:参数校验失败 → 自定义提示。
第三道防线:数据库类型不匹配 → SQL 报错。
2、@Valid / @Validated 参数校验注解何时生效
(1)如果类型不匹配则不需要参数校验,springboot就会直接抛出异常
类型不匹配 本质上是 Spring 在绑定参数(数据类型转换)阶段 就失败了,这一步发生在校验(@Valid / @Validated)之前 。
所以:
就算你没加
@Validated
,只要类型对不上,Spring 也会直接抛异常(比如MethodArgumentTypeMismatchException
或HttpMessageNotReadableException
)。
@Validated
(或@Valid
)只是触发 Bean Validation (如@NotNull
、@Min
)的规则检查,它是在类型绑定成功之后才执行的。
**(2)**参数校验触发顺序
类型绑定阶段(Spring MVC 把字符串、JSON等 转成 Java 类型)
如果转换失败(类型不匹配),直接抛异常,不会进入校验阶段。
对应异常:
JSON → Java 对象失败 →
HttpMessageNotReadableException
URL 参数绑定失败 →
MethodArgumentTypeMismatchException
Bean Validation 校验阶段(@Valid / @Validated)
只会在 类型绑定成功后才执行
触发异常:
@RequestBody
Bean 校验失败 →MethodArgumentNotValidException
方法参数校验失败 →
ConstraintViolationException
**(3)**举例
java@GetMapping("/test") public String test(@RequestParam @Min(0) Integer age) { return "ok"; }
请求 结果 原因 /test?age=abc
MethodArgumentTypeMismatchException
绑定失败,根本没到校验 /test?age=-1
ConstraintViolationException
绑定成功,校验失败(@Min 触发)
所以总结:
类型不匹配 → 不需要
@Validated
,绑定阶段就抛错规则不符合 (值合法性) → 需要
@Valid
/@Validated
触发 Bean Validation参数校验详解(JSR-303 + Hibernate Validator,Spring Boot 常用实践)
一、为什么用 JSR-303(Hibernate Validator)
- JSR-303(现在规格名通常叫 Bean Validation / Jakarta Validation)定义了注解式校验规范,Hibernate Validator 是它的参考实现。Spring Boot 推荐用
spring-boot-starter-validation
快速引入。
二、请求处理的"先后顺序"(非常关键)
HTTP 请求体(JSON) → 先由 Jackson 把 JSON 反序列化为 DTO(POJO)。
- 如果 JSON 字段类型不合(比如 DTO 中
Integer age
,JSON 里"age":"abc"
),Jackson 会抛出反序列化异常 (InvalidFormatException
等),这会被 Spring 包装成HttpMessageNotReadableException
,并返回 400。这一步发生在 Bean 验证之前 。所以@Valid
根本还没跑到。反序列化成功后 ,Spring 对 DTO 执行 Bean Validation(JSR-303) ,也就是
@NotNull
、@Min
等注解生效,会抛MethodArgumentNotValidException
(用于@RequestBody
场景)。Baeldung on Kotlin方法级参数校验 (如
@RequestParam
、@PathVariable
、方法参数上的注解)需要配合@Validated
启用,失败会抛ConstraintViolationException
。HomeBaeldung on Kotlin所以核心结论:类型转换(Jackson)错误 ≠ 校验失败,两者需要分别处理才不会出现"看起来没生效"的情况。
三、常见注解(选你常用的)
@NotNull
:不能为 null(适合对象、包装类型)。
@NotBlank
:字符串非 null 且有非空白字符。
@NotEmpty
:集合/字符串非空。
@Min/@Max
:数值最小/最大。
@Positive/@PositiveOrZero
:正数 / 非负数。
@Size(min=, max=)
:字符串/集合长度。
@Pattern
:正则。
@Past
、@Future
、@Digits
等。(更多及差异可参考 Hibernate Validator 文档。)
四、最典型的代码示例(完整流程)
4.1 依赖(Maven)
XML<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
4.2 DTO
javaimport jakarta.validation.constraints.*; public class UserDTO { @NotNull(message = "年龄不能为空") @Min(value = 0, message = "年龄不能小于0") private Integer age; @NotBlank(message = "姓名不能为空") private String name; // getters/setters }
4.3 Controller(@RequestBody + 校验)
java@RestController @RequestMapping("/user") public class UserController { @PostMapping("/add") public ResponseEntity<String> addUser(@Valid @RequestBody UserDTO user) { // 到这里说明反序列化成功且校验通过 return ResponseEntity.ok("ok"); } }
4.4 Controller(method-param 验证:@RequestParam / @PathVariable)
java@RestController @Validated // 必须有这个,才能对方法参数上的注解生效 public class DemoController { @GetMapping("/age") public String getByAge(@RequestParam @Min(0) Integer age) { return "age=" + age; } }
如果
?age=abc
会在数据绑定阶段报类型转换错误(MethodArgumentTypeMismatchException
/TypeMismatchException
),需单独处理;如果?age=-1
则通过参数校验会抛出ConstraintViolationException
。
五、统一异常处理(推荐)------把 400 错误给前端友好化
下面的
@RestControllerAdvice
覆盖常见场景:反序列化失败、DTO 校验失败、方法参数校验失败、类型不匹配。
javaimport com.fasterxml.jackson.databind.exc.InvalidFormatException; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.*; import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; @RestControllerAdvice public class GlobalExceptionHandler { // 1) @RequestBody 的校验失败(@Valid)-> BindingResult -> MethodArgumentNotValidException // 触发条件: //发生在 @RequestBody + @Valid/@Validated 校验 Java Bean 的场景。 //前端传的 JSON 能成功反序列化为 Java 对象,但字段值不符合校验注解的规则(@NotNull、@Min 等)。 @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<Map<String, String>> handleValidationExceptions(MethodArgumentNotValidException ex) { Map<String, String> errors = new HashMap<>(); ex.getBindingResult().getFieldErrors() .forEach(error -> errors.put(error.getField(), error.getDefaultMessage())); return ResponseEntity.badRequest().body(errors); } // 2) JSON 反序列化 / 类型转换(例如 age: "abc" -> Integer)导致的异常 // 触发条件 //发生在 反序列化 JSON 到 Java 对象时失败,通常是字段类型不匹配或 JSON 格式错误。 //Spring 在还没进入参数校验阶段就失败了(对象根本没创建出来)。 @ExceptionHandler(HttpMessageNotReadableException.class) public ResponseEntity<String> handleJsonParse(HttpMessageNotReadableException ex) { Throwable cause = ex.getCause(); if (cause instanceof InvalidFormatException) { InvalidFormatException ife = (InvalidFormatException) cause; String path = ife.getPathReference(); // like [Field 'age'] String targetType = ife.getTargetType().getSimpleName(); return ResponseEntity.badRequest().body(path + " 无法转换为 " + targetType); } return ResponseEntity.badRequest().body("JSON 解析错误"); } // 3) URL 参数类型不匹配(?age=abc 绑定到 Integer)或 PathVariable 类型不匹配 // 触发条件 //发生在 URL 查询参数 / PathVariable 类型转换失败(即非 JSON 场景)。 //Spring MVC 在绑定参数时,类型不匹配直接抛这个异常。 @ExceptionHandler(MethodArgumentTypeMismatchException.class) public ResponseEntity<String> handleTypeMismatch(MethodArgumentTypeMismatchException ex) { return ResponseEntity.badRequest().body( "参数 " + ex.getName() + " 的类型应该是 " + ex.getRequiredType().getSimpleName() ); } // 4) 方法级参数(@Validated)触发的 ConstraintViolationException(例如 @RequestParam @Min) // 触发条件 //发生在 方法级别参数校验失败(@Validated + @RequestParam / @PathVariable / @ModelAttribute 上的约束注解)。 //必须类上有 @Validated 才会触发方法级参数校验。 @ExceptionHandler(javax.validation.ConstraintViolationException.class) public ResponseEntity<Map<String, String>> handleConstraintViolation(javax.validation.ConstraintViolationException ex) { Map<String, String> errors = new HashMap<>(); ex.getConstraintViolations().forEach(cv -> { String path = cv.getPropertyPath().toString(); errors.put(path, cv.getMessage()); }); return ResponseEntity.badRequest().body(errors); } }
这套处理能把**类型转换错误("abc")和校验失败(-1 / 不满足注解)**分开并返回可读信息。参考了常见的最佳实践。
5.1区别例子
1️⃣
HttpMessageNotReadableException
触发时机:
场景 :
@RequestBody
接收 JSON阶段 :Spring 把请求体(JSON) → Java 对象的 反序列化阶段(Jackson)
原因:JSON 格式错误、字段类型对不上
例子
java@PostMapping("/user") public void addUser(@RequestBody UserDTO dto) { } public class UserDTO { private Integer age; }
请求:
bash{"age": "abc"}
Spring 读取 JSON 发现
"abc"
不能转成 Integer还没到参数校验阶段 ,直接在 Jackson 解析时抛
HttpMessageNotReadableException
📌 关键词 :
@RequestBody
+ JSON + 类型不匹配/格式错
2️⃣
MethodArgumentTypeMismatchException
触发时机:
场景 :
@RequestParam
/@PathVariable
/@ModelAttribute
阶段:Spring 把 URL 参数绑定到方法参数时(不是 JSON,是 query/path/form)
原因:URL 上的值不能转换为目标类型
例子
java@GetMapping("/age") public String getAge(@RequestParam Integer age) { return "..."; }
请求:
bashGET /age?age=abc
Spring 拿到
"abc"
要转成 Integer转换失败 → 抛
MethodArgumentTypeMismatchException
📌 关键词:URL 参数绑定 + 类型不匹配
3️⃣ 核心区别表
特性 HttpMessageNotReadableException MethodArgumentTypeMismatchException 数据来源 JSON 请求体 ( @RequestBody
)URL 参数 / Path 变量 解析阶段 JSON 反序列化(Jackson) 参数绑定(Spring MVC 类型转换器) 常见触发 JSON 语法错 / 字段类型不对 URL 参数值类型不对 示例 {"age": "abc"}
→ Integer/age?age=abc
→ Integer
4️⃣ 记忆口诀
JSON 进来的错 = HttpMessageNotReadableException
URL 上的错 = MethodArgumentTypeMismatchException5.2总结成对照表
异常类型 典型触发场景 数据来源 数据问题 MethodArgumentNotValidException
@RequestBody
+@Valid/@Validated
校验 Java BeanJSON 反序列化成功,但字段值不合法 HttpMessageNotReadableException
@RequestBody
JSON 转 Java Bean 失败JSON JSON 结构错误或字段类型不匹配 MethodArgumentTypeMismatchException
@RequestParam
/@PathVariable
类型转换失败URL 参数 值不能转换成目标类型 ConstraintViolationException
方法参数级别校验失败 URL 参数/PathVariable/Form 值不符合校验规则( @Min
等)
六、@Valid与@Validated区别,以及何时用哪个?
6.1 二者区别
(6.1.1)DTO +
@Valid
(针对对象字段校验)
java@PostMapping("/addUser") public void addUser(@Valid @RequestBody UserDTO user) { // ... }
校验对象 :
UserDTO
里的字段(age
、name
等)。触发条件 :参数是一个 Java Bean(DTO),
@RequestBody
场景下需要加@Valid
才会对这个对象的每个字段进行 JSR-303 校验。失败异常 :
MethodArgumentNotValidException
(适用于 JSON 反序列化成功后的 Bean 校验)。作用范围:只能校验这个对象的字段,不会自动校验单独的方法参数。
适合前端一次传多个字段的场景(比如注册、修改资料)。
(6.1.2) 方法参数 +
@Validated
(针对单个参数校验)注意:
Validated要加在@RequestParam前不行,必须加在类上,但是可以加在
@RequestBody前面,也就是下面**" ②何时用哪个?"中说的**
java@RestController @Validated // 必须有这个才能生效 public class DemoController { @GetMapping("/age") public String getByAge(@RequestParam @Min(0) Integer age) { return "age=" + age; } }
校验对象 :单个方法参数(
age
)。触发条件 :类上或方法上有
@Validated
,Spring 才会在调用方法前对每个参数执行 JSR-303 校验。失败异常 :
ConstraintViolationException
(方法参数校验失败时抛出)。作用范围:适合 GET 请求、查询参数、路径参数等"单字段"校验。
适合这种接口:
GET /age?age=10
,不需要传整个 JSON 对象。6.2 何时用哪个?
(6.2.1)Bean 校验(两者都行)
java@PostMapping("/addUser") public void addUser(@Valid @RequestBody UserDTO user) { ... } // 或 @PostMapping("/addUser") public void addUser(@Validated @RequestBody UserDTO user) { ... }
这里 @Valid 和 @Validated 等效,随便哪个都行。
(6.2.2)方法参数校验(只能用 @Validated而且要加载类上)
java@RestController @Validated // 必须有这个 public class DemoController { @GetMapping("/age") public String getByAge(@RequestParam @Min(0) Integer age) { return "age=" + age; } }
这里如果用 @Valid ,
@Min
是不会生效的。
(6.2.3)选用建议
对象字段校验 → 用哪个都行(习惯上
@Valid
更常见,因为它是 JSR 标准)。方法参数校验 → 用
@Validated
。嵌套对象 → 在嵌套字段上用
@Valid
。
(6.2.4)核心区别对照表
场景 触发注解 校验目标 适用参数类型 常见异常 使用场景 DTO 对象校验 @Valid
Bean 内部字段 @RequestBody
接收的 Java BeanMethodArgumentNotValidException
JSON 传对象,批量字段校验 方法参数校验 @Validated
单个方法参数 @RequestParam
、@PathVariable
、@RequestHeader
等ConstraintViolationException
GET 请求、路径参数、单字段校验
七、常见问题与小结(快速答疑)
前端传
"age":"abc"
时@Valid
为什么没生效?因为 Jackson 在反序列化阶段就失败了(类型不匹配),Bean 验证无机会运行。需要在
HttpMessageNotReadableException
中处理并返回友好提示。Baeldung on Kotlin想让
@RequestParam
也校验怎么办?在 Controller 类或方法上加
@Validated
,并在参数上写注解(如@Min
)。失败会抛ConstraintViolationException
。Home如何给校验信息国际化/自定义消息?
在
ValidationMessages.properties
或messages.properties
中定义键,然后在注解中写message = "{你的键}"
。(官方与社区都有示例。)企业最佳实践(推荐):
使用 DTO + 注解(JSR-303)做规则校验;
统一
@RestControllerAdvice
处理各种异常(反序列化、校验、类型不匹配);把业务逻辑假设的"合法输入"放在校验层拦截,保证 service 层收到的都是干净数据。
SpringBoot--SpringBoot参数校验与类型转换异常
你我约定有三2025-08-12 10:43
相关推荐
笑衬人心。7 分钟前
缓存的三大问题分析与解决XiangCoder28 分钟前
🔥Java核心难点:对象引用为什么让90%的初学者栽跟头?二闹39 分钟前
LambdaQueryWrapper VS QueryWrapper:安全之选与灵活之刃得物技术39 分钟前
Rust 性能提升“最后一公里”:详解 Profiling 瓶颈定位与优化|得物技术用户849137175471641 分钟前
JDK 17 实战系列(第7期):迁移指南与最佳实践XiangCoder44 分钟前
Java编程案例:从数字翻转到成绩统计的实用技巧duration~1 小时前
SpringAI实现Reread(Advisor)aiopencode1 小时前
iOS 文件管理全流程实战,从开发调试到数据迁移我们从未走散1 小时前
面试题-----微服务业务YuforiaCode1 小时前
24SpringCloud黑马商城微服务整合Seata重启服务报错的解决办法