Spring Boot multipartfile always null

25,966

Solution 1

I found out that the problem was the way I was building my request with Retrofit.

Spring's multipart resolver requires the filename for the file to be present in content-disposition field of the part. Without this, it doesn't add the file into the multipart request.

According to the info found here: https://futurestud.io/blog/retrofit-2-how-to-upload-files-to-server, my API interface should be:

@Multipart
@POST("users/{username}/profilePhoto")
Call<Void> uploadProfilePhoto(@Path("username") String username,
                              @Part MultipartBody.Part profilePhoto);

And then when making the call in my test:

// Given
String usernamme = usernames[0];
Resource testImageResource = context.getResource("classpath:images/test_image.jpg");
File imageFile = testImageResource.getFile();
RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), imageFile);
MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", imageFile.getName(), requestFile);

// When
Response<Void> response = testApi.uploadProfilePhoto(usernamme, filePart).execute();

Solution 2

You have to enable the Spring Multipart Resolver as by default Spring doesn't enable the multipart capability.

By default, Spring does no multipart handling, because some developers want to handle multiparts themselves. You enable Spring multipart handling by adding a multipart resolver to the web application’s context.

To your configuration class you would want to add the following bean:

@Bean
public MultipartResolver multipartResolver() {
    return new CommonsMultipartResolver();
}

*** Update *** As my previous answer was not correct based on the comments. Here is an updated example that I was able to run successfully.

@SpringBootApplication
public class StackoverflowWebmvcSandboxApplication {
    public static void main(String[] args) {
        SpringApplication.run(StackoverflowWebmvcSandboxApplication.class, args);
    }

    @Controller
    public class UploadPhoto {
        @PostMapping("{username}/profilePhoto")
        public ResponseEntity<String> saveProfilePhoto(@PathVariable("username") String username,
                @RequestPart(name = "file", required = false) MultipartFile imageFile, HttpServletRequest request) {
            String body = "MultipartFile";
            if (imageFile == null) {
                body = "Null MultipartFile";
            }

            return ResponseEntity.status(HttpStatus.CREATED).body(body);
        }
    }
}

It is a very basic test with no special stuff. I then created a postman request and here is the sample curl call:

curl -X POST -H "Cache-Control: no-cache" -H "Postman-Token: 17e5e6ac-3762-7d45-bc99-8cfcb6dc8cb5" -H "Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW" -F "file=@" "http://localhost:8080/test/profilePhoto"

The response was MultipartFile meaning that it wasn't null and doing a debug on that line showed that the variable was populated with the image that I was uploading.

Solution 3

Check if the following line is present in your application.properties:

spring.http.multipart.enabled = true

Solution 4

You have to enable the Spring Multipart Resolver as by default Spring doesn't enable the multipart capability.

By default, Spring does no multipart handling, because some developers want to handle multiparts themselves. You enable Spring multipart handling by adding a multipart resolver to the web application’s context.

To your configuration class you would want to add the following bean:

@Bean
public MultipartResolver multipartResolver() {
    return new StandardServletMultipartResolver();
}

Since spring boot 1.4+ uses servlet 3.0+ we can leverage on StandardServletMultipartResolver instead of classic CommonsMultipartResolver

Share:
25,966
JabariP
Author by

JabariP

Updated on July 11, 2022

Comments

  • JabariP
    JabariP almost 2 years

    I am using Spring Boot version = '1.4.0.RC1' with Spring Boot Stormpath 1.0.2.

    I am trying to use multipart file upload but the MultipartFile is always null in the controller.

    When I use @RequestPart("file") the info: "status":400,"error":"Bad Request","exception":"org.springframework.web.multipart.support.MissingServletRequestPartException","message":"Required request part 'file' is not present"

    When I use @RequestPart(name = "file", required = false), the part is always null.

    However, if I add an HttpServletRequest argument to the controller, I can get the file part directly from the request, so I know that it is actually present.

    This is the controller and in the code below checkNotNull(part) always succeeds and checkNotNull(imageFile) always fails:

    @PostMapping("{username}/profilePhoto")
    public ResponseEntity<?> saveProfilePhoto(@PathVariable("username") String username,
                                              @RequestPart(name = "file", required = false) MultipartFile imageFile,
                                              HttpServletRequest request) {
        try {
            Part part = request.getPart("file");
            checkNotNull(part);
            checkNotNull(imageFile);
        } catch (IOException | ServletException ex) {
            throw InternalServerErrorException.create();
        }
    
        // Transfer the multipart file to a temp file
        File tmpFile;
        try {
            tmpFile = File.createTempFile(TMP_FILE_PREFIX, null);
            imageFile.transferTo(tmpFile);
        } catch (IOException ex) {
            log.error("Failed to create temp file", ex);
            throw InternalServerErrorException.create();
        }
    
        // Execute the use case
        updateUserProfilePhoto.execute(username, tmpFile);
    
        // Delete the temp file
        FileUtils.deleteQuietly(tmpFile);
    
        return ResponseEntity.status(HttpStatus.CREATED).build();
    }
    

    My integration test uses retrofit:

    @Multipart
    @POST("users/{username}/profilePhoto")
    Call<Void> uploadProfilePhoto(@Path("username") String username,
                                  @Part("file") RequestBody profilePhoto);
    
    ...
    
    @Test
    public void saveProfilePhoto_shouldSavePhoto() throws IOException {
        // Given
        String usernamme = usernames[0];
        Resource testImageResource = context.getResource("classpath:images/test_image.jpg");
        File imageFile = testImageResource.getFile();
        RequestBody body = RequestBody.create(okhttp3.MediaType.parse("image/*"), imageFile);
    
        // When
        Response<Void> response = getTestApi().uploadProfilePhoto(usernamme, body).execute();
    
        // Then
        assertThat(response.code()).isEqualTo(201);
    }
    

    I am using auto configuration so my only custom config class configures Stormpath:

    @Configuration
    public class SpringSecurityWebAppConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.apply(stormpath());
        }
    }
    

    UPDATE: This is the outgoing request. I am not sure how to enable logging in the multipart resolver itself.

    2016-08-18 14:44:14.714 DEBUG 13088 --- [           main] c.t.server.web.testutil.TestConfig$1     : --> POST http://localhost:8080/users/user1/profilePhoto http/1.1
    2016-08-18 14:44:14.714 DEBUG 13088 --- [           main] c.t.server.web.testutil.TestConfig$1     : Content-Type: multipart/form-data; boundary=fe23ef21-3413-404c-a260-791c6921b2c6
    2016-08-18 14:44:14.715 DEBUG 13088 --- [           main] c.t.server.web.testutil.TestConfig$1     : Content-Length: 181212
    2016-08-18 14:44:14.715 DEBUG 13088 --- [           main] c.t.server.web.testutil.TestConfig$1     : Accept: application/json
    2016-08-18 14:44:14.715 DEBUG 13088 --- [           main] c.t.server.web.testutil.TestConfig$1     : Authorization: Bearer [token]
    2016-08-18 14:44:14.715 DEBUG 13088 --- [           main] c.t.server.web.testutil.TestConfig$1     : 
    2016-08-18 14:44:14.735 DEBUG 13088 --- [           main] c.t.server.web.testutil.TestConfig$1     : --fe23ef21-3413-404c-a260-791c6921b2c6
    Content-Disposition: form-data; name="file"
    Content-Transfer-Encoding: binary
    Content-Type: image/*
    Content-Length: 180999
    
    file data
    
    --fe23ef21-3413-404c-a260-791c6921b2c6--
    
    2016-08-18 14:44:14.762 DEBUG 13088 --- [           main] c.t.server.web.testutil.TestConfig$1     : --> END POST (181212-byte body)
    

    Any ideas on what is happening?

    • Shawn Clark
      Shawn Clark over 7 years
      Can you include what the request payload looks like? Maybe even add debug level logging to the MultipartResolver that you are using to see if it is interpreting the multipart component of the request?
    • JabariP
      JabariP over 7 years
      @shawn-clark This is the outgoing request. I am not sure how to enable logging in the multipart resolver itself. Request headers: Accept: application/json Authorization: Bearer [auth token] Request body: --56436527-d311-4d26-8e67-27fdf6f0edb8 Content-Disposition: form-data; name="file" Content-Transfer-Encoding: binary Content-Type: image/* Content-Length: 180999 [... binary...] --56436527-d311-4d26-8e67-27fdf6f0edb8--
    • rajadilipkolli
      rajadilipkolli about 7 years
      As you are using spring boot 1.4 you can use my solution and it works
  • JabariP
    JabariP over 7 years
    Spring Boot Web MVC does configure a multipart resolver automatically so adding that bean is unnecessary. As a check though, I did add it but still got the same error.
  • Shawn Clark
    Shawn Clark over 7 years
    Ah nice... didn't check for the AutoConfiguration for it but have since found it under MultipartAutoConfiguration. It looks by default to use the StandardServletMultipartResolver. You said you were still getting the error with the CommonsMultipartResolver so it seems it is more an issue with the way the request is coming in.
  • Shawn Clark
    Shawn Clark over 7 years
    Glad you got it going. I was thinking the same but your sample request shows that the name is there as "file".
  • rajadilipkolli
    rajadilipkolli almost 7 years
    Which version of spring boot you are using?
  • rajadilipkolli
    rajadilipkolli almost 7 years
  • Ryan Pelletier
    Ryan Pelletier over 4 years
    I figured out this about Content-Disposition yesterday after about 5 hours :/
  • eduardog3000
    eduardog3000 over 2 years
    If you're using Spring 2.0 or greater, it's now spring.servlet.multipart.enabled = true