18

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?

rjdkolb
  • 10,377
  • 11
  • 69
  • 89
JabariP
  • 340
  • 1
  • 2
  • 8
  • 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? – Shawn Clark Aug 18 '16 at 03:04
  • @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-- – JabariP Aug 18 '16 at 20:08
  • As you are using spring boot 1.4 you can use my solution and it works – rajadilipkolli Feb 17 '17 at 10:17

5 Answers5

5

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.

matepal297
  • 961
  • 1
  • 11
  • 19
Shawn Clark
  • 3,330
  • 2
  • 18
  • 30
  • 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. – JabariP Aug 18 '16 at 02:57
  • 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 Aug 18 '16 at 03:03
5

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

spring.http.multipart.enabled = true
Faysal Ahmed
  • 7,501
  • 5
  • 28
  • 50
Nazrul Kabir
  • 51
  • 1
  • 5
3

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

rajadilipkolli
  • 3,475
  • 2
  • 26
  • 49
2

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();
JabariP
  • 340
  • 1
  • 2
  • 8
0

Please refer to my answer in this URL: Spring Boot multipart upload getting null file object

Basically you have to avoid the default Spring configuration for multipart and avoid to set Content-type or boundary in your request header.

Community
  • 1
  • 1
Alexis Gamarra
  • 4,362
  • 1
  • 33
  • 23