上传MultipartFile至服务器时触发MessageBodyProviderNotFoundException
问题背景
基于Gradle构建的SpringBoot项目包含文件上传功能,后端接口经Postman测试可正常接收文件,但客户端调用时出现错误。已在build.gradle中添加Jersey的multipart依赖。
已添加的依赖
implementation 'org.glassfish.jersey.media:jersey-media-multipart:2.34'
错误堆栈
Caused by: org.glassfish.jersey.message.internal.MessageBodyProviderNotFoundException: MessageBodyWriter not found for media type=multipart/form-data, type=class org.springframework.mock.web.MockMultipartFile, genericType=class org.springframework.mock.web.MockMultipartFile.
at org.glassfish.jersey.message.internal.WriterInterceptorExecutor$TerminalWriterInterceptor.aroundWriteTo(WriterInterceptorExecutor.java:224)
at org.glassfish.jersey.message.internal.WriterInterceptorExecutor.proceed(WriterInterceptorExecutor.java:139)
at org.glassfish.jersey.message.internal.MessageBodyFactory.writeTo(MessageBodyFactory.java:1116)
at org.glassfish.jersey.client.ClientRequest.doWriteEntity(ClientRequest.java:461)
at org.glassfish.jersey.client.ClientRequest.writeEntity(ClientRequest.java:443)
at org.glassfish.jersey.client.internal.HttpUrlConnector._apply(HttpUrlConnector.java:369)
Caused by: org.glassfish.jersey.message.internal.MessageBodyProviderNotFoundException: MessageBodyWriter not found for media type=multipart/form-data, type=class org.springframework.mock.web.MockMultipartFile, genericType=class org.springframework.mock.web.MockMultipartFile.at org.glassfish.jersey.client.internal.HttpUrlConnector.apply(HttpUrlConnector.java:267) at org.glassfish.jersey.client.ClientRuntime.invoke(ClientRuntime.java:297) at org.glassfish.jersey.client.JerseyInvocation.lambda$invoke$2(JerseyInvocation.java:687) at org.glassfish.jersey.client.JerseyInvocation.call(JerseyInvocation.java:697) at org.glassfish.jersey.client.JerseyInvocation.lambda$runInScope$3(JerseyInvocation.java:691) at org.glassfish.jersey.internal.Errors.process(Errors.java:292) at org.glassfish.jersey.internal.Errors.process(Errors.java:274) at org.glassfish.jersey.internal.Errors.process(Errors.java:205) at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:390) at org.glassfish.jersey.client.JerseyInvocation.runInScope(JerseyInvocation.java:691) at org.glassfish.jersey.client.JerseyInvocation.invoke(JerseyInvocation.java:686) at org.glassfish.jersey.client.JerseyInvocation$Builder.method(JerseyInvocation.java:461) at org.glassfish.jersey.client.JerseyInvocation$Builder.post(JerseyInvocation.java:357) at client.utils.ServerUtils.filePostRequest(ServerUtils.java:273) at client.utils.ServerUtils.saveFile(ServerUtils.java:278) at client.scenes.MediaPlayerCtrl.save(MediaPlayerCtrl.java:121) at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104) ... 57 more
客户端代码
public void saveFile(File file) throws IOException { MultipartFile file1=convert(file); filePostRequest("secure/" + store.accessStore().getUsername() + "/file/add", Entity.entity(file1, "multipart/form-data"), new GenericType<>(){}); } public static MultipartFile convert(File file) throws IOException { String filename = file.getName(); FileInputStream inputStream = new FileInputStream(file); return new MockMultipartFile(filename, inputStream); } private <T> void filePostRequest(String path, Entity send, GenericType<T> retType) { ClientBuilder.newClient(new ClientConfig()) .target(getServer()).path(path) .request("multipart/form-data") .post(send, retType); }
服务端Controller代码
@SpringBootApplication @RestController @RequestMapping(value = {"/secure/{username}/{password}/file", "/secure/{username}/file"}) public class FileController { @Autowired private StorageService service; @PostMapping("/add") public ResponseEntity<?> uploadFile(@RequestParam("file") MultipartFile file) throws IOException { String uploadFile = service.uploadFile(file); return ResponseEntity.status(HttpStatus.OK) .body(uploadFile); }
补充问题
尝试添加测试类注册MultiPartFeature,但IDE提示错误:Cannot access jakarta.ws.rs.core.Configurable
package server.api; import org.glassfish.jersey.client.ClientConfig; import org.glassfish.jersey.media.multipart.MultiPartFeature; import org.glassfish.jersey.test.JerseyTest; public class FileControllerTest extends JerseyTest { @Override protected void configureClient(ClientConfig conf) { conf.register(MultiPartFeature.class); } }
解决方案
1. 修正客户端请求构建方式
Jersey客户端无法直接序列化Spring的MockMultipartFile为multipart请求体,需改用Jersey原生的FormDataMultiPart构建请求:
public void saveFile(File file) throws IOException { // 用Jersey API构建multipart请求体 FormDataMultiPart multiPart = new FormDataMultiPart(); try (FileInputStream inputStream = new FileInputStream(file)) { // 注意参数名"file"要和服务端@RequestParam("file")完全一致 multiPart.field("file", inputStream, MediaType.APPLICATION_OCTET_STREAM_TYPE, file.getName()); filePostRequest("secure/" + store.accessStore().getUsername() + "/file/add", Entity.entity(multiPart, multiPart.getMediaType()), new GenericType<>(){}); } finally { multiPart.close(); } } private <T> void filePostRequest(String path, Entity send, GenericType<T> retType) { // 必须将MultiPartFeature注册到ClientConfig ClientConfig config = new ClientConfig(); config.register(MultiPartFeature.class); ClientBuilder.newClient(config) .target(getServer()).path(path) .request(MediaType.APPLICATION_JSON_TYPE) // 此处填写服务端返回的媒体类型,而非请求类型 .post(send, retType); }
2. 移除不必要的类型转换
无需将File转换为Spring的MockMultipartFile,直接用Jersey API处理文件流即可,避免跨框架类型不兼容问题。
3. 排查依赖版本冲突
- 确保
jersey-media-multipart版本与项目中其他Jersey依赖版本一致,避免版本冲突。 - SpringBoot项目建议使用starter管理Jersey依赖,简化版本控制:
implementation 'org.springframework.boot:spring-boot-starter-jersey' implementation 'org.glassfish.jersey.media:jersey-media-multipart:2.34'
4. 解决测试类错误
测试类的错误源于Jersey版本API不兼容:
- 项目使用的Jersey 2.x基于JEE API,需确保测试依赖为2.x版本:
testImplementation 'org.glassfish.jersey.test-framework.providers:jersey-test-framework-provider-grizzly2:2.34'
- 确认测试类导入的
ClientConfig为org.glassfish.jersey.client.ClientConfig,而非Jakarta EE版本的包。
内容的提问来源于stack exchange,提问作者Micu Bogdan

