Multipart File Upload Using Spring Rest Template + Spring Web MVC
Asked Answered
V

6

59

I am trying to upload a File using RestTemplate with the following code.

   MultiValueMap<String, Object> multipartMap = new LinkedMultiValueMap<>();
   multipartMap.add("file", new ClassPathResource(file));

   HttpHeaders headers = new HttpHeaders();
   headers.setContentType(new MediaType("multipart", "form-data"));

   HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<MultiValueMap<String, Object>>(multipartMap, headers);

   System.out.println("Request for File Upload : " + request);

   ResponseEntity<byte[]> result = template.get().exchange(
                    contextPath.get() + path, HttpMethod.POST, request,
                    byte[].class);

I have the MultipartResolver bean and Controller code is

@RequestMapping(value = "/{id}/image", method = RequestMethod.POST)
@ResponseStatus(HttpStatus.NO_CONTENT)
@Transactional(rollbackFor = Exception.class)
public byte[] setImage(@PathVariable("id") Long userId,
        @RequestParam("file") MultipartFile file) throws IOException {
    // Upload logic
}

And I get the following Exception

 org.springframework.web.bind.MissingServletRequestParameterException: Required MultipartFile parameter 'file' is not present
        at org.springframework.web.method.annotation.RequestParamMethodArgumentResolver.handleMissingValue(RequestParamMethodArgumentResolver.java:255) ~[spring-web-4.0.6.RELEASE.jar:4.0.6.RELEASE]
        at org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver.resolveArgument(AbstractNamedValueMethodArgumentResolver.java:95) ~[spring-web-4.0.6.RELEASE.jar:4.0.6.RELEASE]
        at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:79) ~[spring-web-4.0.6.RELEASE.jar:4.0.6.RELEASE]
        at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:157) [spring-web-4.0.6.RELEASE.jar:4.0.6.RELEASE]
        at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:124) [spring-web-4.0.6.RELEASE.jar:4.0.6.RELEASE]
        at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:104) [spring-webmvc-4.0.6.RELEASE.jar:4.0.6.RELEASE]
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:749) [spring-webmvc-4.0.6.RELEASE.jar:4.0.6.RELEASE]
        at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:689) [spring-webmvc-4.0.6.RELEASE.jar:4.0.6.RELEASE]
        at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:83) [spring-webmvc-4.0.6.RELEASE.jar:4.0.6.RELEASE]
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:938) [spring-webmvc-4.0.6.RELEASE.jar:4.0.6.RELEASE]
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:870) [spring-webmvc-4.0.6.RELEASE.jar:4.0.6.RELEASE]
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:961) [spring-webmvc-4.0.6.RELEASE.jar:4.0.6.RELEASE]
        at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:863) [spring-webmvc-4.0.6.RELEASE.jar:4.0.6.RELEASE]
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:646) [tomcat-embed-core-7.0.54.jar:7.0.54]
        at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:837) [spring-webmvc-4.0.6.RELEASE.jar:4.0.6.RELEASE]
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:727) [tomcat-embed-core-7.0.54.jar:7.0.54]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303) [tomcat-embed-core-7.0.54.jar:7.0.54]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208) [tomcat-embed-core-7.0.54.jar:7.0.54]
        at org.springframework.web.filter.ShallowEtagHeaderFilter.doFilterInternal(ShallowEtagHeaderFilter.java:80) [spring-web-4.0.6.RELEASE.jar:4.0.6.RELEASE]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.0.6.RELEASE.jar:4.0.6.RELEASE]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241) [tomcat-embed-core-7.0.54.jar:7.0.54]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208) [tomcat-embed-core-7.0.54.jar:7.0.54]
        at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:77) [spring-web-4.0.6.RELEASE.jar:4.0.6.RELEASE]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.0.6.RELEASE.jar:4.0.6.RELEASE]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241) [tomcat-embed-core-7.0.54.jar:7.0.54]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208) [tomcat-embed-core-7.0.54.jar:7.0.54]
        at org.apache.logging.log4j.web.Log4jServletFilter.doFilter(Log4jServletFilter.java:67) [log4j-web-2.0.2.jar:2.0.2]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241) [tomcat-embed-core-7.0.54.jar:7.0.54]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208) [tomcat-embed-core-7.0.54.jar:7.0.54]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
        at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:118) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
        at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:84) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
        at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:113) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
        at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:103) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
        at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:113) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
        at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:154) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
        at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:45) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
        at m.m.m.AbstractPreAuthenticatedProcessingFilter.doFilter(UapAbstractPreAuthenticatedProcessingFilter.java:109) [classes/:?]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
        at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:50) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-4.0.6.RELEASE.jar:4.0.6.RELEASE]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
        at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:87) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
        at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
        at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:160) [spring-security-web-3.2.5.RELEASE.jar:3.2.5.RELEASE]
        at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:344) [spring-web-4.0.6.RELEASE.jar:4.0.6.RELEASE]
        at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:261) [spring-web-4.0.6.RELEASE.jar:4.0.6.RELEASE]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241) [tomcat-embed-core-7.0.54.jar:7.0.54]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208) [tomcat-embed-core-7.0.54.jar:7.0.54]
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220) [tomcat-embed-core-7.0.54.jar:7.0.54]
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122) [tomcat-embed-core-7.0.54.jar:7.0.54]
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171) [tomcat-embed-core-7.0.54.jar:7.0.54]
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102) [tomcat-embed-core-7.0.54.jar:7.0.54]
        at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:950) [tomcat-embed-core-7.0.54.jar:7.0.54]
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116) [tomcat-embed-core-7.0.54.jar:7.0.54]
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408) [tomcat-embed-core-7.0.54.jar:7.0.54]
        at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1040) [tomcat-embed-core-7.0.54.jar:7.0.54]
        at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:607) [tomcat-embed-core-7.0.54.jar:7.0.54]
        at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:314) [tomcat-embed-core-7.0.54.jar:7.0.54]
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) [?:1.7.0_67]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) [?:1.7.0_67]
        at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-7.0.54.jar:7.0.54]
        at java.lang.Thread.run(Thread.java:745) [?:1.7.0_67]
Veradia answered 17/11, 2014 at 2:39 Comment(6)
What's the URL you're accessing that generates the error?Automation
Doing a POST to localhost:8080/myweb/50/image. It is reaching the controller method but file is missing. Don't know why.Veradia
Please share the multipartResolver configuration.Spinifex
see here - something similarDaisydaitzman
You declared a request parameter named file, the error message complains about a missing request parameter file and you don't have one in the url you shared. My guess is that is the issue. Also can you try to debug it and see if it enters the setImage() function? This will help determine whether the issue is with mapping to the method or inside it.Automation
You can visit bellow thread. It has full working code with description: https://mcmap.net/q/119331/-spring-resttemplate-exception-handlingAccordingly
V
60

The Multipart File Upload worked after following code modification to Upload using RestTemplate

LinkedMultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
map.add("file", new ClassPathResource(file));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);

HttpEntity<LinkedMultiValueMap<String, Object>> requestEntity = new    HttpEntity<LinkedMultiValueMap<String, Object>>(
                    map, headers);
ResponseEntity<String> result = template.get().exchange(
                    contextPath.get() + path, HttpMethod.POST, requestEntity,
                    String.class);

And adding MultipartFilter to web.xml

    <filter>
        <filter-name>multipartFilter</filter-name>
        <filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>multipartFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
Veradia answered 24/11, 2014 at 4:18 Comment(5)
If the template object is of type RestTemplate, same 400 response I am gettingBrownfield
I am using the above code in rest client for uploading a file to a rest web service(on another server different from my local machine) but, getting the error as: I/O error on POST request for "anothermachine:31112/url/path": class path resource [fileName.csv] cannot be resolved to URL because it does not existCherellecheremis
@Veradia I also have similar question here in which I am trying to upload tar.gz file but its not working. Can you help me out?Assurance
I'm stumped by the question and this answer. Neither should compile because file is a MultipartFile but new ClassPathResource() requires a String.Ophiolatry
file is actually a String containing the name of the file to be uploaded which is available in the Classpath. Not a java.io.File. Obviously the code works for many.Veradia
L
31

For most use cases, it's not correct to register MultipartFilter in web.xml because Spring MVC already does the work of processing your multipart request. It's even written in the filter's javadoc.

On the server side, define a multipartResolver bean in your app context:

@Bean
public CommonsMultipartResolver multipartResolver(){
    CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver();
    commonsMultipartResolver.setDefaultEncoding("utf-8");
    commonsMultipartResolver.setMaxUploadSize(50000000);
    return commonsMultipartResolver;
}

On the client side, here's how to prepare the request for use with Spring RestTemplate API:

    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.MULTIPART_FORM_DATA);

    LinkedMultiValueMap<String, String> pdfHeaderMap = new LinkedMultiValueMap<>();
    pdfHeaderMap.add("Content-disposition", "form-data; name=filex; filename=" + file.getOriginalFilename());
    pdfHeaderMap.add("Content-type", "application/pdf");
    HttpEntity<byte[]> doc = new HttpEntity<byte[]>(file.getBytes(), pdfHeaderMap);

    LinkedMultiValueMap<String, Object> multipartReqMap = new LinkedMultiValueMap<>();
    multipartReqMap.add("filex", doc);

    HttpEntity<LinkedMultiValueMap<String, Object>> reqEntity = new HttpEntity<>(multipartReqMap, headers);
    ResponseEntity<MyResponse> resE = restTemplate.exchange(uri, HttpMethod.POST, reqEntity, MyResponse.class);

The important thing is really to provide a Content-disposition header using the exact case, and adding name and filename specifiers, otherwise your part will be discarded by the multipart resolver.

Then, your controller method can handle the uploaded file with the following argument:

@RequestParam("filex") MultipartFile file

Hope this helps.

Linzy answered 24/8, 2018 at 13:36 Comment(5)
Thanx a lott man..I was looking for the client side for days...Thanks very muchCohabit
For whatever reason, I needed quotes around the name and filename: "form-data; name=\"file\"; filename=\"" + filePart.getSubmittedFileName() + "\"")Corkboard
I was getting error of boundary not set but this post helped me, thanksUlland
I get an error saying "[org.springframework.web.multipart.support.MissingServletRequestPartException: Required request part 'file' is not present]" Has anyone faced this?Reich
Setting the custom multipart resolver solved my problem, thanks!! Just a side note, I had to import the commons-fileupload dependency and everything woked fine.Modernize
G
20

Here are my working example

@RequestMapping(value = "/api/v1/files/upload", method =RequestMethod.POST)
public ResponseEntity<?> upload(@RequestParam("files") MultipartFile[] files) {
    LinkedMultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
    List<String> tempFileNames = new ArrayList<>();
    String tempFileName;
    FileOutputStream fo;

    try {
        for (MultipartFile file : files) {
            tempFileName = "/tmp/" + file.getOriginalFilename();
            tempFileNames.add(tempFileName);
            fo = new FileOutputStream(tempFileName);
            fo.write(file.getBytes());
            fo.close();
            map.add("files", new FileSystemResource(tempFileName));
        }

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.MULTIPART_FORM_DATA);

        HttpEntity<LinkedMultiValueMap<String, Object>> requestEntity = new HttpEntity<>(map, headers);
        String response = restTemplate.postForObject(uploadFilesUrl, requestEntity, String.class);

    } catch (IOException e) {
        e.printStackTrace();
    }

    for (String fileName : tempFileNames) {
        File f = new File(fileName);
        f.delete();
    }
    return new ResponseEntity<Object>(HttpStatus.OK);
}
Gangboard answered 11/7, 2016 at 14:4 Comment(1)
Instead of storing the files locally, you can pass the MultipartFiles through by using file.getResource() instead of new FileSystemResource(tempFileName). The for loop would look like this: for (MultipartFile file : files) {map.add("files", file.getResource); }Lashondalashonde
F
13

A correct file upload would like this:

HTTP header:

Content-Type: multipart/form-data; boundary=ABCDEFGHIJKLMNOPQ

Http body:

--ABCDEFGHIJKLMNOPQ

Content-Disposition: form-data; name="file"; filename="my.txt"

Content-Type: application/octet-stream

Content-Length: ...

<...file data in base 64...>

--ABCDEFGHIJKLMNOPQ--

and code is like this:

public void uploadFile(File file) {
        try {
            RestTemplate restTemplate = new RestTemplate();
            String url = "http://localhost:8080/file/user/upload";
            HttpMethod requestMethod = HttpMethod.POST;

            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(MediaType.MULTIPART_FORM_DATA);


            MultiValueMap<String, String> fileMap = new LinkedMultiValueMap<>();
            ContentDisposition contentDisposition = ContentDisposition
                    .builder("form-data")
                    .name("file")
                    .filename(file.getName())
                    .build();

            fileMap.add(HttpHeaders.CONTENT_DISPOSITION, contentDisposition.toString());
            HttpEntity<byte[]> fileEntity = new HttpEntity<>(Files.readAllBytes(file.toPath()), fileMap);

            MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
            body.add("file", fileEntity);

            HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);

            ResponseEntity<String> response = restTemplate.exchange(url, requestMethod, requestEntity, String.class);

            System.out.println("file upload status code: " + response.getStatusCode());

        } catch (IOException e) {
            e.printStackTrace();
        }

}
Fantasist answered 29/8, 2020 at 18:31 Comment(4)
Best (most complete) answer I've found, thanks!Scary
How could an additional (POJO) be sent with the fields sent as request parameters?Roundsman
This solution worked for me without using any additional libraries. Thanks a lot for the answerSophomore
The unabridged version of this answer can be found here: medium.com/red6-es/…Subject
L
12

For those who are getting the error as:

I/O error on POST request for "anothermachine:31112/url/path";: class path 
resource [fileName.csv] cannot be resolved to URL because it does not exist.

It can be resolved by using the

LinkedMultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
map.add("file", new FileSystemResource(file));

If the file is not present in the classpath, and an absolute path is required.

Lajoie answered 22/12, 2017 at 16:18 Comment(1)
My issue was a bit different: ClassPathResource was used for a file that was not accessible in classpath, and changing it to FileSystemResource made my day. Thanks for pointing out.Derm
T
1

More based on the feeling, but this is the error you would get if you missed to declare a bean in the context configuration, so try adding

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <property name="maxUploadSize" value="10000000"/>
</bean>
Tantalize answered 17/11, 2014 at 9:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.