前言:
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 对象失败 →
HttpMessageNotReadableExceptionURL 参数绑定失败 →
MethodArgumentTypeMismatchExceptionBean Validation 校验阶段(@Valid / @Validated)
只会在 类型绑定成功后才执行
触发异常:
@RequestBodyBean 校验失败 →MethodArgumentNotValidException方法参数校验失败 →
ConstraintViolationException
**(3)**举例
java@GetMapping("/test") public String test(@RequestParam @Min(0) Integer age) { return "ok"; }
请求 结果 原因 /test?age=abcMethodArgumentTypeMismatchException绑定失败,根本没到校验 /test?age=-1ConstraintViolationException绑定成功,校验失败(@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@RequestBodyJSON 转 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 对象校验 @ValidBean 内部字段 @RequestBody接收的 Java BeanMethodArgumentNotValidExceptionJSON 传对象,批量字段校验 方法参数校验 @Validated单个方法参数 @RequestParam、@PathVariable、@RequestHeader等ConstraintViolationExceptionGET 请求、路径参数、单字段校验
七、常见问题与小结(快速答疑)
前端传
"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
相关推荐
一 乐8 小时前
远程在线诊疗|在线诊疗|基于java和小程序的在线诊疗系统小程序设计与实现(源码+数据库+文档)缺点内向8 小时前
Java:高效删除Excel中的空白行和列pkowner8 小时前
若依使用技巧静若繁花_jingjing8 小时前
DDD领域驱动设计实践_保险serendipity_hky8 小时前
【微服务 - easy视频 | day04】Seata解决分布式事务沿着路走到底8 小时前
python 判断与循环楼田莉子8 小时前
Linux学习:进程的控制大菠萝学姐8 小时前
基于springboot的旅游攻略网站设计与实现回家路上绕了弯8 小时前
服务器大量请求超时?从网络到代码的全链路排查指南zbhbbedp282793cl9 小时前
unique_ptr和shared_ptr有何区别?