You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在JSON请求到达Spring控制器前校验值避免返回400状态码

Spring JSON参数类型不匹配自定义错误返回实现方案

当JSON请求字段类型和实体类定义不匹配时,触发的400错误是Jackson反序列化失败抛出的HttpMessageNotReadableException导致的,此时请求还未进入控制器逻辑,可以通过全局异常处理器捕获该异常,解析错误字段后返回自定义响应,具体实现如下:

1. 核心实现:捕获JSON反序列化异常

使用@RestControllerAdvice定义全局异常处理器,专门处理JSON解析类错误:

import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.HashMap;
import java.util.Map;

@RestControllerAdvice
public class GlobalJsonExceptionHandler {

    @ExceptionHandler(HttpMessageNotReadableException.class)
    public ResponseEntity<Map<String, Object>> handleJsonFormatError(HttpMessageNotReadableException e) {
        Map<String, Object> resp = new HashMap<>();
        resp.put("code", 400001);
        resp.put("message", "请求JSON格式错误");
        Map<String, String> fieldErrors = new HashMap<>();

        // 解析Jackson类型不匹配错误
        if (e.getCause() instanceof InvalidFormatException formatException) {
            for (JsonMappingException.Reference ref : formatException.getPath()) {
                String field = ref.getFieldName();
                String expectType = formatException.getTargetType().getSimpleName();
                String actualValue = formatException.getValue().toString();
                fieldErrors.put(field, String.format("类型错误,预期为%s,实际输入值为%s", expectType, actualValue));
            }
        }
        resp.put("fieldErrors", fieldErrors);
        return new ResponseEntity<>(resp, HttpStatus.BAD_REQUEST);
    }
}

实现效果示例,当请求传入{"username": 1234, "pin": "johndoe"}时,返回的响应结构如下:

{
  "code": 400001,
  "message": "请求JSON格式错误",
  "fieldErrors": {
    "username": "类型错误,预期为String,实际输入值为1234",
    "pin": "类型错误,预期为Integer,实际输入值为johndoe"
  }
}

2. 扩展:搭配业务参数校验

如果需要同时覆盖非格式类的业务校验(比如用户名长度、pin码范围),可以搭配JSR-380参数校验实现:

  • 引入校验依赖(Spring Boot 3+需要单独引入,低版本默认包含在web starter中):
    <!-- Maven 依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
    
  • 给实体类加校验注解:
    import jakarta.validation.constraints.NotBlank;
    import jakarta.validation.constraints.NotNull;
    import jakarta.validation.constraints.Size;
    
    public class CreateUserRequest {
        @NotBlank(message = "用户名不能为空")
        @Size(min = 3, max = 20, message = "用户名长度需在3-20位之间")
        private String username;
    
        @NotNull(message = "pin码不能为空")
        private Integer pin;
    
        // 省略getter、setter
    }
    
  • 控制器接口加@Valid注解开启校验:
    @PostMapping("/createUser")
    public String createUser(@RequestBody @Valid CreateUserRequest request) {
        // 业务逻辑处理
        return "ok";
    }
    
  • 全局异常处理器补充参数校验异常捕获逻辑:
    import org.springframework.validation.FieldError;
    import org.springframework.web.bind.MethodArgumentNotValidException;
    
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, Object>> handleParamValidateError(MethodArgumentNotValidException e) {
        Map<String, Object> resp = new HashMap<>();
        resp.put("code", 400002);
        resp.put("message", "请求参数校验失败");
        Map<String, String> fieldErrors = new HashMap<>();
        for (FieldError error : e.getBindingResult().getFieldErrors()) {
            fieldErrors.put(error.getField(), error.getDefaultMessage());
        }
        resp.put("fieldErrors", fieldErrors);
        return new ResponseEntity<>(resp, HttpStatus.BAD_REQUEST);
    }
    

注意:以上实现对所有@RequestBody的JSON接口生效,不需要修改原有接口逻辑,嵌套实体类的字段类型错误也可以正常识别。

内容的提问来源于stack exchange,提问作者Hunter Yavitz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 19:15:05