Spring Framework 7.1.0-RC1(Boot 4.2.0-RC1)新增 MultipartHttpMessageConverter,可读写 multipart。RestClient 把 multipart/form-data 响应解成 MultiValueMap<String, Part>,读到 fieldPart=fieldValue 与文件 logo.txt 内容 logo-bytes-demo。只留 FormHttpMessageConverter 时抛 UnknownContentTypeException。

官方 7.1 Release Notes「Improved Multipart support」写明:此前 HTTP 客户端靠 FormHttpMessageConverter 发 multipart,但读不了 multipart 响应,也无法用 RestTestClient 完整测 multipart 端点;7.1 起 FormHttp 只管 URL-encoded,multipart 交给新转换器。类上标注 @since 7.1。对照 jar:spring-web 7.0.0 无此类,7.1.0-RC1 有 org.springframework.http.converter.multipart.MultipartHttpMessageConverter。

版本:JDK 21.0.12.1、Spring Boot 4.2.0-RC1、Spring Framework / spring-web 7.1.0-RC1。控制器 GET /upload 返回带 fieldPart、filePart(classpath logo.txt)的 MultiValueMap,produces = multipart/form-data。

@GetMapping(path = "/upload", produces = MediaType.MULTIPART_FORM_DATA_VALUE)
ResponseEntity<MultiValueMap<String, Object>> upload() {
  MultiValueMap<String, Object> parts = new LinkedMultiValueMap<>();
  parts.add("fieldPart", "fieldValue");
  parts.add("filePart", new ClassPathResource("static/logo.txt"));
  return ResponseEntity.ok().contentType(MediaType.MULTIPART_FORM_DATA).body(parts);
}

应用启动后 RestClient 解析:

2026-10-26T08:14:05.905+08:00  INFO 346267 --- [multipart-http-converter-demo] [           main] com.example.MultipartDemoApplication     : Started MultipartDemoApplication in 1.17 seconds (process running for 1.45)
=== RESTCLIENT_MULTIPART_PARSE_BEGIN ===
parts.keys=[fieldPart, filePart]
fieldPart.class=org.springframework.http.converter.multipart.DefaultParts$DefaultFormFieldPart
fieldPart.name=fieldPart
fieldPart.value=fieldValue
fieldPart.contentType=text/plain;charset=UTF-8
filePart.class=org.springframework.http.converter.multipart.DefaultParts$DefaultFilePart
filePart.name=filePart
filePart.filename=logo.txt
filePart.contentType=text/plain
filePart.bytes.length=15
filePart.bytes.text=logo-bytes-demo
raw.status=200
raw.contentType=multipart/form-data;boundary=6aWn2FL9RMHhzVAwoVjKpK5QeQ8bcWgePEeCL
raw.body.length=384
=== RESTCLIENT_MULTIPART_PARSE_END ===

转换器能力:

FormHttp.supported=[application/x-www-form-urlencoded]
MultipartHttp.supported=[multipart/form-data, multipart/mixed, multipart/related]
FormHttp.canRead(MultiValueMap<String,Part>, multipart/form-data)=false
MultipartHttp.canRead(MultiValueMap<String,Part>, multipart/form-data)=true
FormHttp.canRead(MultiValueMap<String,String>, application/x-www-form-urlencoded)=true
MultipartHttp.canRead(MultiValueMap<String,String>, application/x-www-form-urlencoded)=false

旧路径对照:禁用默认转换器后只加 FormHttp,再去掉 ClientBuilder 自动附带的 MultipartHttp(源码推断:converters 非空时会 new MultipartHttpMessageConverter),RestClient 读同一 URL:

formOnly.converters.beforeStrip=[FormHttpMessageConverter, MultipartHttpMessageConverter]
formOnly.converters.afterStrip=[FormHttpMessageConverter]
formOnly.exception=org.springframework.web.client.UnknownContentTypeException
formOnly.message=Could not extract response: no suitable HttpMessageConverter found for response type [org.springframework.util.MultiValueMap<java.lang.String, org.springframework.http.converter.multipart.Part>] and content type [multipart/form-data;boundary=NaemXDW6SiZHK2qPWjtkp1rkj2OvvNv]

RestTestClient 绑控制器(与 Spring Framework 7 的 RestTestClient 能绑 MockMvc 也能连真端口,MVC 项目不用再为测试引 WebFlux 同类绑定)测同一端点:

=== RESTTESTCLIENT_MULTIPART_BEGIN ===
resttest.keys=[fieldPart, filePart]
resttest.fieldPart.value=fieldValue
resttest.filePart.filename=logo.txt
resttest.filePart.contentType=text/plain
resttest.filePart.bytes.text=logo-bytes-demo
=== RESTTESTCLIENT_MULTIPART_END ===

依据:Spring Framework 7.1 Release Notes(HTTP 200)、参考文档 Rest Clients / RestTestClient(HTTP 200)。

— 感谢阅读 —
广告

一起交流

分享你的思考,让讨论更进一步。