接口地址和 POST 方法都没写错,请求也能到服务器,返回的却是 415 Unsupported Media Type。这时先保存请求头和原始请求体:服务端可能不接受这份请求体的媒体类型,也可能找不到能把它读成目标 Java 类型的转换器。
本文按 Spring Boot 3.x、Spring MVC、JDK 17 及以上说明,以 Spring Framework 6.2 文档核对行为。WebFlux 和 Spring Boot 4 的配置要对应各自版本。下面的接口用于独立测试项目,不执行真实业务写入。
先分清四种经常混在一起的响应
| 响应 | 优先核对 |
|---|---|
| 404 | 路径、上下文路径、代理转发和实际映射 |
| 405 | HTTP 方法与控制器允许的方法 |
| 415 | 请求的 Content-Type、请求体格式、consumes、读取转换器 |
| 406 | Accept 与服务端能够生成的响应媒体类型 |
Content-Type 描述这次发送的内容;Accept 表示调用方希望收到什么。把 Accept 改成 JSON,不能把表单请求体变成 JSON。上述状态只是入口线索,网关或异常处理器也可能自定义响应,所以还要看实际返回方和异常链。Spring MVC 的请求媒体类型匹配明确区分了 consumes 与 produces。
先从浏览器 Network 或调用端日志保存 URL、方法、这两个请求头和请求体样本。业务数据需要脱敏;不要只看封装库里“准备发送”的 Java 对象。
用一个只回显的接口核对 JSON 请求
已有 spring-boot-starter-web 且保留默认 JSON 支持的测试项目,可以加入下面的控制器。把包名调整到启动类扫描范围内。
package demo.web;
import java.util.Map;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class FormatProbeController {
public record Probe(String name) {}
@PostMapping(path = "/probe/format",
consumes = MediaType.APPLICATION_JSON_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public Map<String, String> probe(@RequestBody Probe input) {
return Map.of("received", input.name());
}
}
创建 UTF-8 文件 payload.json,内容如下:
{"name":"format-probe"}
下面是 Bash 命令,端口按本地测试项目调整。Windows PowerShell 可用 curl.exe,并把命令合并成一行。
curl -i http://127.0.0.1:8080/probe/format \
-H 'Content-Type: application/json' \
-H 'Accept: application/json' \
--data-binary @payload.json
然后只改变请求媒体类型,保留同一文件和同一地址:
curl -i http://127.0.0.1:8080/probe/format \
-H 'Content-Type: text/plain' \
--data-binary @payload.json
在未自定义错误映射的该测试接口中,第二次请求应因 consumes 不匹配返回 415;第一条用来验证 JSON 链路。这里给的是可复现方法与预期,不是本站生产环境的测试记录。若第一条也失败,继续检查实际映射、依赖和异常,不先删除全部媒体类型约束。
若媒体类型已匹配,但 JSON 少了引号、日期无法转换或字段结构不对,常见的是消息读取异常和 400。这与“不接受这类请求体”要分开处理。Spring 的请求体读取依赖消息转换器,最终状态仍以项目的异常处理为准。
JSON、表单和文件上传分别对照
前端把对象交给某个 HTTP 库,并不保证线上一定按 JSON 发出。重点检查拦截器有没有统一改头、序列化是否执行,以及代理有没有修改请求。
- JSON 请求:请求体确实是 JSON,服务端按
@RequestBodyDTO 读取。 - 普通表单:通常使用
application/x-www-form-urlencoded,按表单参数或对象绑定设计接口。 - 文件上传:使用
multipart/form-data和正确 boundary,由 multipart 机制解析;不要把整个上传请求标成 JSON。
使用浏览器 FormData 时,通常让浏览器生成带 boundary 的请求头。手写一个不带匹配 boundary 的总 Content-Type,容易把正常文件上传变成解析失败。带 JSON 元数据的 @RequestPart 还需要检查该部分自己的 Content-Type;总请求是 multipart,并不代表每一部分都被标成 JSON。Spring MVC multipart 文档说明了这条读取路径。
若问题集中在文件上传,可结合上传 413 与 MultipartFile 为空检查大小限制和参数名;方法不匹配则看接口返回 405。
头和请求体都对,再看转换器
异常里出现 HttpMediaTypeNotSupportedException 时,结合目标方法的参数类型和 supported media types 判断。重点回顾最近是否做过这些修改:排除了 JSON 依赖、自行添加 @EnableWebMvc、在 MVC 配置中替换了转换器列表,或注册了只支持部分类型的自定义转换器。
支持 application/json 还不够,转换器也要能读取这个 Java 类型。不要把“支持 JSON”和“任何 DTO 都能成功反序列化”当成一回事。消息转换器文档列出了常见实现。
如果问题只在经过网关时出现,用同一份请求文件分别请求允许访问的应用地址和网关地址,比较状态及请求关联日志。直连成功只能缩小范围,还需要找到具体哪个环节改了请求。
修复后保留三组回归请求
至少保留“约定的正确格式”“错误媒体类型”“媒体类型正确但内容格式错误”三组请求,再对比直连和代理路径。修复应让正确请求成功,也让错误请求继续得到清晰、符合接口契约的响应。
只把 consumes 扩成 */*,可能掩盖调用端错误,转换器仍未必能读取内容。把参数改成 String 再手工解析,则会改变校验和错误处理路径,应作为接口设计决定处理。
参考资料
更新记录
- 2026-10-29:初稿;按 Spring 官方资料核对,示例用于独立测试项目,未声称已在本站生产服务运行。
Spring Boot 接口返回 415 怎么排查
https://java.li/archives/spring-boot-http-415
评论