When trying to send a multipart request over http interfaces, multipart is sent directly instead of resource and causing the following on error on server side.

org.apache.tomcat.util.http.fileupload.MultipartStream$MalformedStreamException: Stream ended unexpectedly
    at org.apache.tomcat.util.http.fileupload.MultipartStream.readHeaders(MultipartStream.java:520) ~[tomcat-embed-core-10.1.7.jar:10.1.7]
    at org.apache.tomcat.util.http.fileupload.impl.FileItemIteratorImpl.findNextItem(FileItemIteratorImpl.java:243) ~[tomcat-embed-core-10.1.7.jar:10.1.7]
    at org.apache.tomcat.util.http.fileupload.impl.FileItemIteratorImpl.<init>(FileItemIteratorImpl.java:142) ~[tomcat-embed-core-10.1.7.jar:10.1.7]
    at org.apache.tomcat.util.http.fileupload.FileUploadBase.getItemIterator(FileUploadBase.java:252) ~[tomcat-embed-core-10.1.7.jar:10.1.7]
    at org.apache.tomcat.util.http.fileupload.FileUploadBase.parseRequest(FileUploadBase.java:276) ~[tomcat-embed-core-10.1.7.jar:10.1.7]
    at org.apache.catalina.connector.Request.parseParts(Request.java:2790) ~[tomcat-embed-core-10.1.7.jar:10.1.7]
    at org.apache.catalina.connector.Request.getParts(Request.java:2691) ~[tomcat-embed-core-10.1.7.jar:10.1.7]
    at org.apache.catalina.connector.RequestFacade.getParts(RequestFacade.java:774) ~[tomcat-embed-core-10.1.7.jar:10.1.7]

And on the client side after error, trying to serialize multipart and getting the jackson error.

With a custom resolver or by changing interface parameter from multipart to resource problem can be solved but a built in support would be awesome. Example for client: https://github.com/oguder/spring-webclient-multipart

Server side is pretty straightforward.

@PostMapping("/test")
public String test(@RequestPart(name = "file") MultipartFile file, @RequestPart(name = "info") AdditionalInfo additionalInfo) {
    return String.format("file name = %s and size %d KB additional name = %s", file.getOriginalFilename(), file.getSize() / 1024, additionalInfo.getName());
}

Comment From: rstoyanchev

I suppose if you're on the server side, you may have a MultipartFile already and it's convenient to pass that in. We could update RequestPartArgumentResolver along those lines. However, wouldn't it make more sense for the HTTP client interface to declare Resource anyway. It's more generally applicable and not limited to server-side usage by requiring a MultipartFile input. Your controller method would simply get the resource from the MultipartFile:

@PostMapping("/test")
public String test(@RequestPart(name = "file") MultipartFile file, @RequestPart(name = "info") AdditionalInfo additionalInfo) {
    return testApi.test(file.getResource(), additionalInfo);
}

Comment From: oguder

We have a case interfaces are in library and client can not modify. We were able to handle it with a custom resolver but custom resolvers are more prone to bugs compared to libraries. That is why, I have created the request.

Comment From: rstoyanchev

Adding support for MultipartFile in MultipartBodyBuilder would address this and also be more broadly helpful.

Comment From: amand1996

I would like to pick this up if no one has picked it up.

Comment From: OlgaMaciaszek

@amand1996 thanks, we're working on it already.

Comment From: rstoyanchev

On closer look, MultipartBodyBuilder is in a lower level package than MultipartFile and therefore can't refer to it. We'll add an argument resolver instead.

Comment From: rstoyanchev

Superseded by #30728.

Comment From: MarkusRohlof

For anyone else stranding here and wondering what to make of this: Starting with Spring Boot 3.2.0-M1 (or just spring-web 6.1.0-M2) you can now have a PostExchange with a MultipartFile like this:

    @PostExchange("/images")
    fun uploadImage(
        img: MultipartFile
    )

It seems that specifying the contentType in PostExchange is at least unnecessary, while annotating the MultipartFile as @RequestPart leads to an Exception because the wrong ArgumentResolver tries to resolve the Argument.

Comment From: rstoyanchev

Thanks for the feedback, @MarkusRohlof. I've created #31139 to address the exception you mentioned.