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

如何处理单个@RequestBody抛出的多个HttpMessageNotReadableException?

解决方案

实现原理

默认Jackson反序列化时遇到第一个错误就会终止流程并抛出HttpMessageNotReadableException,因此只能捕获到首个错误。要实现全量错误收集,需修改Jackson反序列化配置,关闭快速失败逻辑,同时自定义处理器收集反序列化全流程的所有字段错误,最后统一抛出包含所有错误的自定义异常。

具体实现步骤

1. 自定义反序列化错误收集处理器

继承Jackson的DeserializationProblemHandler,在各类反序列化错误回调中收集字段错误信息,返回true让反序列化流程继续执行:

public class CollectAllDeserializationErrorsHandler extends DeserializationProblemHandler {
    private final Map<String, String> fieldErrors = new HashMap<>();

    // 处理枚举、日期等字符串转目标类型错误
    @Override
    public JavaType handleWeirdStringValue(DeserializationContext ctxt, Class<?> targetType, String valueToConvert, String failureMsg) throws IOException {
        String currentField = ctxt.getParser().getParsingContext().getCurrentName();
        if (targetType.isEnum()) {
            String allowedValues = Arrays.stream(targetType.getEnumConstants())
                    .map(Object::toString)
                    .collect(Collectors.joining(","));
            fieldErrors.put(currentField, String.format("非法枚举值[%s],允许值:%s", valueToConvert, allowedValues));
        } else if (LocalDate.class.isAssignableFrom(targetType)) {
            fieldErrors.put(currentField, String.format("日期格式错误[%s],请使用yyyy-MM-dd格式", valueToConvert));
        } else {
            fieldErrors.put(currentField, String.format("值格式错误:%s", failureMsg));
        }
        // 跳过当前字段的赋值,继续处理后续字段
        return super.handleWeirdStringValue(ctxt, targetType, valueToConvert, failureMsg);
    }

    // 处理未知字段错误
    @Override
    public boolean handleUnknownProperty(DeserializationContext ctxt, JsonParser p, JsonDeserializer<?> deserializer, Object beanOrClass, String propertyName) throws IOException {
        fieldErrors.put(propertyName, "未知请求字段");
        // 继续处理后续字段
        return true;
    }

    public Map<String, String> getFieldErrors() {
        return fieldErrors;
    }

    public boolean hasErrors() {
        return !fieldErrors.isEmpty();
    }
}

2. 自定义请求反序列化异常

用于封装收集到的所有字段错误:

public class RequestDeserializationException extends RuntimeException {
    private final Map<String, String> fieldErrors;

    public RequestDeserializationException(Map<String, String> fieldErrors) {
        this.fieldErrors = fieldErrors;
    }

    public Map<String, String> getFieldErrors() {
        return fieldErrors;
    }
}

3. 自定义Jackson消息转换器

替换Spring默认的MappingJackson2HttpMessageConverter,在反序列化时注入错误收集器,完成后如果有错误就抛出自定义异常:

public class CustomJacksonMessageConverter extends MappingJackson2HttpMessageConverter {
    @Override
    public Object read(Type type, Class<?> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
        // 拷贝全局ObjectMapper避免修改全局配置
        ObjectMapper objectMapper = getObjectMapper().copy();
        CollectAllDeserializationErrorsHandler errorHandler = new CollectAllDeserializationErrorsHandler();
        objectMapper.addHandler(errorHandler);
        // 关闭反序列化快速失败配置
        objectMapper.disable(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE);
        objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

        Object result = objectMapper.readValue(inputMessage.getBody(), objectMapper.constructType(type));
        // 反序列化完成后检查是否有收集到的错误
        if (errorHandler.hasErrors()) {
            throw new RequestDeserializationException(errorHandler.getFieldErrors());
        }
        return result;
    }
}

4. 注册自定义消息转换器到Spring MVC

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        // 将自定义转换器放在最前面,优先生效
        converters.add(0, new CustomJacksonMessageConverter());
    }
}

5. 新增自定义异常处理器

@ExceptionHandler(RequestDeserializationException.class)
public ResponseEntity<Map<String, Object>> handleDeserializationErrors(RequestDeserializationException e) {
    Map<String, Object> resp = new HashMap<>();
    resp.put("code", 400);
    resp.put("message", "请求参数格式错误");
    resp.put("errors", e.getFieldErrors());
    return ResponseEntity.badRequest().body(resp);
}

效果验证

当传入的请求体同时包含非法枚举值和错误格式日期时,响应示例如下:

{
    "code": 400,
    "message": "请求参数格式错误",
    "errors": {
        "speed": "非法枚举值[XXX],允许值:FAST,SLOW,NORMAL",
        "startDate": "日期格式错误[2020/01/01],请使用yyyy-MM-dd格式"
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.05 06:39:02