54

I have a Controller like this and I want to submit a form with file uploading as well as some form data like label as shown below. Also, I want to do that using @RequestBody so I can use the @Valid annotation on the wrapper as more variables will be added.

public @ResponseBody WebResponse<Boolean> updateEUSettings(
    final Locale locale,
    @Validated @ModelAttribute final EUPSettingsWrapper endUserPortalSettingsWrapper) {
}

And my wrapper is:

public class EUPSettingsWrapper {

    private String label;
    private MultipartFile logo;
// getter , setters..etc...
}

But I would like to convert it into a @RequestBody from ModelAttributes.

The way I'm trying is by having the file upload separated as request parameter like this:

public @ResponseBody WebResponse<Boolean> updateEUSettings(
    final Locale locale,
    @Validated @RequestBody final EUPSettingsWrapper endUserPortalSettingsWrapper, 
    @RequestParam(value = "file1", required = true) final MultipartFile logo) {

    endUserPortalSettingsWrapper.setLogo(logo);

    // ...
}

In my mock MVC, I am setting:

getMockMvc().perform(fileUpload(uri).file(logo)
                        .accept(MediaType.APPLICATION_JSON)
                        .content(JSONUtils.toJSON(wrapper))
                        .contentType(MediaType.MULTIPART_FORM_DATA))
                        .andExpect(status().isOk());

But I'm getting an error like this which says:

org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'multipart/form-data' not supported

Does anyone have a good idea of how Multipart file uploads can be used with @RequestBody? Anything I am doing wrong above?

nikiforovpizza
  • 487
  • 1
  • 7
  • 13
Yatin
  • 821
  • 1
  • 8
  • 15

5 Answers5

53

You can actually simplify your life here since all you are doing is submitting a form that contains some fields and file. You don't need @RequestBody for what you are trying to do. You can use regular Spring MVC features, so your controller method would look like:

@ResponseBody
public WebResponse<Boolean> updateEUSettings(
     Locale locale, 
     @Valid EUPSettingsWrapper endUserPortalSettingsWrapper, 
     @RequestParam(value = "file1", required = true) MultipartFile logo
) {


}

The client that submits the request to this controller will need to have a form with enctype="multipart/form-data".

In your Spring MVC test you would write something like this:

getMockMvc().perform(fileUpload(uri).file("file1", "some-content".getBytes()) 
                        .param("someEuSettingsProperty", "someValue")
                        .param("someOtherEuSettingsProperty", "someOtherValue")
                        .accept(MediaType.APPLICATION_JSON)
                        .contentType(MediaType.MULTIPART_FORM_DATA))
                        .andExpect(status().isOk());
Max Farsikov
  • 2,451
  • 19
  • 25
geoand
  • 60,071
  • 24
  • 172
  • 190
  • 1
    Very interesting, but how does the data looks in the Ajax POST request? Do you create a Object to represent EUPSettingsWrapper and a FormData to represent the MultipartFile and then you assign the FormData to, let's say, obj.logo ? Also do you JSON.stringify() that obj or not? – Christos Karapapas Apr 19 '19 at 17:50
  • Unfortunately I don't really remember since the answer is pretty old :) – geoand Apr 20 '19 at 17:26
  • Another way is encode your file bytes into base64 and then generated string can be use in request json, generally this patten is not recommended but for special schenarios: {"name":"aa","logo":encodeBase64(fileByteArray)} for encoding follow given url: https://stackoverflow.com/questions/22172604/convert-image-url-to-base64/22172860 – Dharmendrasinh Chudasama Sep 23 '20 at 10:32
12

Please add the following bean in your spring-servlet.xml to add the support for multipart request.

<bean id="multipartResolver"
    class="org.springframework.web.multipart.commons.CommonsMultipartResolver" />

Also don't forget to add the dependency for commons-fileupload jar

Ankur Jain
  • 1,386
  • 3
  • 17
  • 27
  • Also see this answer for how to set it up in a Java-based configuration: https://stackoverflow.com/a/29883166/3960852 – Kenny Worden Jul 10 '18 at 17:50
12

I couldn't find a way to use @RequestBody.

However, you can do something like this:

@RequestMapping(value = "/uploadStuff", method = RequestMethod.POST)
public MyViewDto doStuff(@RequestPart("json") @Valid MyDto dto,
                         @RequestPart("file") MultipartFile file) { ... }

You can test it like this:

MockMultipartFile jsonFile = new MockMultipartFile("json", "",
            "application/json", "{}".getBytes());
MockMultipartFile dataFile = new MockMultipartFile("file", "foo.zip", "application/octet-stream", bytes);

mockMvc.perform(fileUpload("/uploadStuff")
            .file(dataFile)
            .file(jsonFile))
            .andExpect(status().isOk());
Gueorgui Obregon
  • 5,077
  • 3
  • 33
  • 57
hookumsnivy
  • 231
  • 2
  • 8
  • 2
    org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/octet-stream' not supported – Akshay Naik May 05 '20 at 10:41
-1

I struggled a little with this and ended up passing as simple parameters. Fine if you don't have lots to pass in your request:

myMethod(@RequestParam("file") MultipartFile myFile,
        @RequestParam("param1") Float param1, @RequestParam("param2") String param2 {}
Andy B
  • 629
  • 8
  • 15
-2

For Spring 4 and later you can do the following to get the full object:

public ResponseEntity<Object> upload(@Payload EUPSettingsWrapper wrapper) {

}

Note: Also should work without the tag

public ResponseEntity<Object> upload(EUPSettingsWrapper wrapper) {

}
Jason Glez
  • 1,254
  • 1
  • 16
  • 16