I have a file uploading api which was working perfectly fine under the spring boot
version 2.1.13
. After upgrading the version to 2.5.2
, it started to throw an exception. Looking at the changelogs, I couldn't see anything significant changes that's related to Multipart
processing. What could I be missing here? Below are the sample codes I have.
Exception
org.springframework.web.multipart.support.MissingServletRequestPartException: Required request part 'file' is not present
at org.springframework.web.servlet.mvc.method.annotation.RequestPartMethodArgumentResolver.resolveArgument(RequestPartMethodArgumentResolver.java:161) ~[spring-webmvc-5.3.8.jar:5.3.8]
at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:121) ~[spring-web-5.3.8.jar:5.3.8]
at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:170) ~[spring-web-5.3.8.jar:5.3.8]
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:137) ~[spring-web-5.3.8.jar:5.3.8]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106) ~[spring-webmvc-5.3.8.jar:5.3.8]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:894) ~[spring-webmvc-5.3.8.jar:5.3.8]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808) ~[spring-webmvc-5.3.8.jar:5.3.8]
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.3.8.jar:5.3.8]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1063) ~[spring-webmvc-5.3.8.jar:5.3.8]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963) ~[spring-webmvc-5.3.8.jar:5.3.8]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) [spring-webmvc-5.3.8.jar:5.3.8]
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909) [spring-webmvc-5.3.8.jar:5.3.8]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:681) [tomcat-embed-core-9.0.48.jar:4.0.FR]
application.properties
spring.servlet.multipart.enabled=true
spring.servlet.multipart.max-file-size=20MB
spring.servlet.multipart.max-request-size=20MB
controller end point
@PostMapping(
value = "/upload",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE
)
public ResponseEntity<Object> uploadFile(@RequestPart("file") MultipartFile file) {
...
}
Request Payload Sample
POST http://localhost:8080/upload
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryGG9dgUb5THDV0eDB
------WebKitFormBoundaryGG9dgUb5THDV0eDB
Content-Disposition: form-data; name="file"; filename="Sample.pdf"
Content-Type: application/pdf
------WebKitFormBoundaryGG9dgUb5THDV0eDB--
Note: I don't have any MultipartResolver
bean defined in my configuration. I tried adding the MultipartResolver bean definition as follows (only one at a time) but didn't seem to resolve the issue.
@Bean
public CommonsMultipartResolver multipartResolver() { // didn't work
return new CommonsMultipartResolver();
}
@Bean
public StandardServletMultipartResolver multipartResolver() { // didn't work
return new StandardServletMultipartResolver();
}
It turns out this issue was affected after the Spring Boot 2.2
. Since that version, the filter HttpHiddenMethodFilter
was disabled by default. The issue got fixed after enabling the filter in application.properties
.
spring.mvc.hiddenmethod.filter.enabled=true
My Findings in Detail
The purpose of the above filter has nothing to do with the error I was getting. But the request parts
was getting initialized as a side effect of executing the filter. More specifically, when the filter tries to retrieve the _method
parameter value (e.g. request.getParameter("_method")
, the getParameter
method of the request
instance internally seems to parse the parameters which then initializes the request parts
. So when the filter was disabled in spring-boot
version 2.2, there was nothing to initialize the request parts
.
I feel like the request parts
initialization should be fixed within the Spring
framework itself. But until then, either we could enable the HttpHiddenMethodFilter
filter, or we could define a custom filter that takes care of initializing the request parts
, something like below:
@Configuration
@Order(Ordered.HIGHEST_PRECEDENCE)
public class RequestInitializerFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
request.getParameterNames(); // this takes care of initializing request `parts`
filterChain.doFilter(request, response);
}
}