bigywater.tech
Spring · 161

HttpMessageNotReadableException:请求体常见错误各对应什么报错

请求体解析失败报 HttpMessageNotReadableException,到底哪里错了?哪些看着不对的请求体其实能通过?客户端能看到什么?这篇在 Spring Boot 4.0.8 上把 22 种请求体逐个发一遍。

5 / 22
22 种请求体里悄悄返回 200 的个数:数字字符串、小数 1.5(被截成 1)、BOM、重复键、未知字段
17 种
被拒绝的请求体都抛 HttpMessageNotReadableException,原因在最内层的 Jackson 异常里
Failed to read request
开启 problemdetails 后,四种不同的解析错误响应里的 detail 是同一句话

能通过的

实测 · 本机执行输出
  共 22 种请求体,返回 200 的 5 种,返回 400 等的 17 种
  能通过:数字字符串 {"n":"1"} 传给 int → ok n=1 s=null d=null c=null l=null b=false
  能通过:小数 {"n":1.5} 传给 int → ok n=1 s=null d=null c=null l=null b=false
  能通过:开头带 BOM → ok n=1 s=null d=null c=null l=null b=false
  能通过:重复的键 {"n":1,"n":2} → ok n=2 s=null d=null c=null l=null b=false
  能通过:多余的未知字段 {"zzz":1} → ok n=0 s=null d=null c=null l=null b=false

数字字符串、1.5 截断、BOM、重复键、未知字段都被接受。

被拒绝的

实测 · 本机执行输出
  请求体为空 → 400;HttpMessageNotReadableException → 最内层 HttpMessageNotReadableException:Required request body is missing: public java.lang.String demo.Main.p(demo.Dto)
  JSON 语法错误 {bad → 400;HttpMessageNotReadableException → 最内层 StreamReadException:Unexpected character ('b' (code 98)): was expecting double-quote to start property name
  字符串传给 int 字段 {"n":"abc"} → 400;HttpMessageNotReadableException → 最内层 InvalidFormatException:Cannot deserialize value of type `int` from String "abc": not a valid `int` value
  null 传给 int 字段 {"n":null} → 400;HttpMessageNotReadableException → 最内层 MismatchedInputException:Cannot map `null` into type `int` (set `DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES` to 'false' to allow)
  数值溢出 {"n":99999999999} → 400;HttpMessageNotReadableException → 最内层 InputCoercionException:Numeric value (99999999999) out of range of `int` (-2147483648 - 2147483647)
  枚举值不存在 {"c":"BLUE"} → 400;HttpMessageNotReadableException → 最内层 InvalidFormatException:Cannot deserialize value of type `demo.Color` from String "BLUE": not one of the values accepted for Enum class: [RED, GREEN]
  日期格式不对 {"d":"2026/10/23"} → 400;HttpMessageNotReadableException → 最内层 DateTimeParseException:Text '2026/10/23' could not be parsed at index 4
  字符串传给 List {"l":"x"} → 400;HttpMessageNotReadableException → 最内层 MismatchedInputException:Cannot deserialize value of type `java.util.ArrayList<java.lang.Integer>` from String value (token `JsonToken.VALUE_STRING`)
  List 里元素类型不对 {"l":[1,"a"]} → 400;HttpMessageNotReadableException → 最内层 InvalidFormatException:Cannot deserialize value of type `java.lang.Integer` from String "a": not a valid `java.lang.Integer` value
  布尔值写成 "yes" {"b":"yes"} → 400;HttpMessageNotReadableException → 最内层 InvalidFormatException:Cannot deserialize value of type `boolean` from String "yes": only "true"/"True"/"TRUE" or "false"/"False"/"FALSE" recognized
  对象传给 String {"s":{"a":1}} → 400;HttpMessageNotReadableException → 最内层 MismatchedInputException:Cannot deserialize value of type `java.lang.String` from Object value (token `JsonToken.START_OBJECT`)
  数组传给对象 [1,2] → 400;HttpMessageNotReadableException → 最内层 MismatchedInputException:Cannot deserialize value of type `demo.Dto` from Array value (token `JsonToken.START_ARRAY`)
  末尾多余逗号 {"n":1,} → 400;HttpMessageNotReadableException → 最内层 StreamReadException:Unexpected character ('}' (code 125)): was expecting double-quote to start property name
  单引号 {'n':1} → 400;HttpMessageNotReadableException → 最内层 StreamReadException:Unexpected character (''' (code 39)): was expecting double-quote to start property name
  带注释 {"n":1 /*x*/} → 400;HttpMessageNotReadableException → 最内层 StreamReadException:Unexpected character ('/' (code 47)): maybe a (non-standard) comment? (not recognized as one since Feature 'ALLOW_COMMENTS' not enabled for parser)
  后面多一个 JSON {"n":1}{"n":2} → 400;HttpMessageNotReadableException → 最内层 MismatchedInputException:Trailing token (`JsonToken.START_OBJECT`) found after value (bound as `demo.Dto`): not allowed as per `DeserializationFeature.FAIL_ON_TRAILING_TOKENS`
  非法的 UTF-8 字节 → 400;HttpMessageNotReadableException → 最内层 StreamReadException:Invalid UTF-8 start byte 0xff

顶层同一个异常,最内层按语法、类型、范围分成几类。

响应

实测 · 本机执行输出
  JSON 语法错误 {bad → 响应体 {"timestamp":"2026-10-23T07:42:38.554Z","status":400,"error":"Bad Request","path":"/p"}
  字符串传给 int 字段 {"n":"abc"} → 响应体 {"timestamp":"2026-10-23T07:42:38.557Z","status":400,"error":"Bad Request","path":"/p"}
  null 传给 int 字段 {"n":null} → 响应体 {"timestamp":"2026-10-23T07:42:38.568Z","status":400,"error":"Bad Request","path":"/p"}
  枚举值不存在 {"c":"BLUE"} → 响应体 {"timestamp":"2026-10-23T07:42:38.572Z","status":400,"error":"Bad Request","path":"/p"}
  开启 spring.mvc.problemdetails.enabled=true 之后:JSON 语法错误 {bad → 400 {"detail":"Failed to read request","instance":"/p","status":400,"title":"Bad Request"}
  开启 spring.mvc.problemdetails.enabled=true 之后:字符串传给 int 字段 {"n":"abc"} → 400 {"detail":"Failed to read request","instance":"/p","status":400,"title":"Bad Request"}
  开启 spring.mvc.problemdetails.enabled=true 之后:null 传给 int 字段 {"n":null} → 400 {"detail":"Failed to read request","instance":"/p","status":400,"title":"Bad Request"}
  开启 spring.mvc.problemdetails.enabled=true 之后:枚举值不存在 {"c":"BLUE"} → 400 {"detail":"Failed to read request","instance":"/p","status":400,"title":"Bad Request"}

响应体不带具体原因,要自己提取。

分步演示

请求体
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK 21.0.12(Homebrew) / Spring Boot 4.0.8(Spring Framework 7.0.9、Tomcat 11.0.24) / Spring Cloud 2025.1.3;用 javac 编译、java 直接运行,依赖由 Maven 从 Maven Central 解析;数字每次运行会有小幅抖动(Spring Boot 4.0.8 的 starter-web,Jackson 3.1.5;POST /p,请求头 Content-Type: application/json;一个注册在最前面的 HandlerExceptionResolver 记录异常类与最内层原因,不处理;请求用 http.client 发原始字节)
    • 没有验证:@Valid 校验失败(见 x159 与校验专题);Jackson 配置项(FAIL_ON_UNKNOWN_PROPERTIES 等)改动后的差异,仅在 x150 里测过默认值;Content-Type 带 charset 的编码处理;超大请求体与 Tomcat 的限制;WebFlux 的 DecodingException;Jackson 2 与 3 对同一批请求体的差异;自定义 @ExceptionHandler 的具体写法
    京ICP备2026067502号-1京公网安备11010502063487号