I have a file-upload controller with a method that looks like this:
@RequestMapping(value = "/upload", method = RequestMethod.POST, produces = {"*/*", "application/json"})
public @ResponseBody ScriptUploadResponse upload(@RequestParam("userId") Long userId, @RequestParam("script") MultipartFile file) {
return scriptService.upload(userId, file);
}
This used to work fine in Spring 3 with an XML-based config. I recently moved over to a Java-based config with Spring 4. When I upload a file, I get a 400: Bad request
with the complaint that userId
has not been provided. But when I look at the request in the browser, this is what I see:
------WebKitFormBoundaryoJhTJ817NockqUSY
Content-Disposition: form-data; name="userId"
1
------WebKitFormBoundaryoJhTJ817NockqUSY
Content-Disposition: form-data; name="script"; filename="script.js"
Content-Type: application/javascript
------WebKitFormBoundaryoJhTJ817NockqUSY--
Spring claims:
HTTP Status 400 - Required Long parameter 'userId' is not present
Why is Spring saying that I haven't provided userId
when the payload shows that it is present?
UPDATE
I've put breakpoints inside RequestParamMethodArgumentResolver.java
(an internal Spring class) and I can see that getParts()
on the HttpServletRequest
object returns no parts at all. I'm not sure why this is happening, but it seems to be the root of the issue. From the browser I can see the request being made, but for whatever reason, the multipart data isn't making it through.
I was able to figure this out. To enable support for multipart files, you have to configure things in a certain way. The documentation for this was frustratingly hard to find and Spring's documentation regarding this seems to be incomplete or only related to XML-based config as well. I am not sure if I'm simply looking in the wrong place or what, but even with Google I was unable to find a single place that explains how to set this up. Anyway, here goes.
You first have to include a bean in your web configuration. I simply added the following to my configuration class (that extends WebMvcConfigurerAdapter
):
@Bean
public MultipartResolver multipartResolver() {
return new StandardServletMultipartResolver();
}
But this is not enough. If you are using Servlet 3.0 and also using a Java-based config for that, you have to configure the dispatcher servlet to support multipart files:
I added the following class to my initializer (that extends WebApplicationInitializer
):
dispatcher.setMultipartConfig(
new MultipartConfigElement("/tmp", 25 * 1024 * 1024, 125 * 1024 * 1024, 1 * 1024 * 1024)
);
The entire method ends up looking like this:
@Override
public void onStartup(ServletContext servletContext) {
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(ApplicationConfig.class, WebConfig.class);
servletContext.addListener(new ContextLoaderListener(rootContext));
//Spring security
servletContext.addFilter("springSecurityFilterChain", new DelegatingFilterProxy("springSecurityFilterChain")).addMappingForUrlPatterns(null, false, "/*");
//Enable multipart support
ServletRegistration.Dynamic dispatcher = servletContext.addServlet("DispatcherServlet", new DispatcherServlet(rootContext));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");
dispatcher.setMultipartConfig(
new MultipartConfigElement("/tmp", 25 * 1024 * 1024, 125 * 1024 * 1024, 1 * 1024 * 1024)
);
}