springspring-bootspring-mvcfile-uploadmultipartform-data

Spring Boot 2.5.x: Required request part 'file' is not present


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();
}

Solution

  • 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);
        }
    }