Spring Boot multipartfile always null
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
JabariP
Updated on July 11, 2022Comments
-
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 andcheckNotNull(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 over 7 yearsCan 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 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 about 7 yearsAs you are using spring boot 1.4 you can use my solution and it works
-
-
JabariP over 7 yearsSpring 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 over 7 yearsAh nice... didn't check for the AutoConfiguration for it but have since found it under
MultipartAutoConfiguration
. It looks by default to use theStandardServletMultipartResolver
. You said you were still getting the error with theCommonsMultipartResolver
so it seems it is more an issue with the way the request is coming in. -
Shawn Clark over 7 yearsGlad you got it going. I was thinking the same but your sample request shows that the name is there as "file".
-
rajadilipkolli almost 7 yearsWhich version of spring boot you are using?
-
rajadilipkolli almost 7 yearsgithub.com/rajadilipkolli/spring-mvc-showcase/blob/master/src/… sample code which is working
-
Ryan Pelletier over 4 yearsI figured out this about Content-Disposition yesterday after about 5 hours :/
-
eduardog3000 over 2 yearsIf you're using Spring 2.0 or greater, it's now
spring.servlet.multipart.enabled = true