How to send Multipart form data with restTemplate Spring-mvc
Solution 1
Reading the whole file in a ByteArrayResource
can be a memory consumption issue with large files.
You can proxy a file upload in a spring mvc controller using a InputStreamResource
:
@RequestMapping(value = "/upload", method = RequestMethod.POST)
public ResponseEntity<?> uploadImages(@RequestPart("images") final MultipartFile[] files) throws IOException {
LinkedMultiValueMap<String, Object> map = new LinkedMultiValueMap<>();
String response;
HttpStatus httpStatus = HttpStatus.CREATED;
try {
for (MultipartFile file : files) {
if (!file.isEmpty()) {
map.add("images", new MultipartInputStreamFileResource(file.getInputStream(), file.getOriginalFilename()));
}
}
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
String url = "http://example.com/upload";
HttpEntity<LinkedMultiValueMap<String, Object>> requestEntity = new HttpEntity<>(map, headers);
response = restTemplate.postForObject(url, requestEntity, String.class);
} catch (HttpStatusCodeException e) {
httpStatus = HttpStatus.valueOf(e.getStatusCode().value());
response = e.getResponseBodyAsString();
} catch (Exception e) {
httpStatus = HttpStatus.INTERNAL_SERVER_ERROR;
response = e.getMessage();
}
return new ResponseEntity<>(response, httpStatus);
}
class MultipartInputStreamFileResource extends InputStreamResource {
private final String filename;
MultipartInputStreamFileResource(InputStream inputStream, String filename) {
super(inputStream);
this.filename = filename;
}
@Override
public String getFilename() {
return this.filename;
}
@Override
public long contentLength() throws IOException {
return -1; // we do not want to generally read the whole stream into memory ...
}
}
Solution 2
You are getting the exception because none of RestTemplate's default MessageConverters know how to serialize the InputStream contained by the MultipartFile file. When sending objects via RestTemplate, in most cases you want to send POJOs. You can fix this by adding the bytes of the MultipartFile to the MultiValueMap instead of the MultipartFile itself.
I think there is also something wrong with your servlet part. For instance
File file1 = (File) req.getAttribute("userfile1");
should always return null, as ServletRequest's getAttribute method does not return request/form parameters but attributes set by the servlet context. Are you sure it is actually working with your curl example?
Here is an example of a Spring MVC method forwarding a file to a servlet:
Servlet (though I tested it running in a Spring MVC container), adapted from here:
@RequestMapping("/pi")
private void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
final String path = request.getParameter("destination");
final Part filePart = request.getPart("file");
final String fileName = request.getParameter("filename");
OutputStream out = null;
InputStream fileContent = null;
final PrintWriter writer = response.getWriter();
try {
out = new FileOutputStream(new File(path + File.separator
+ fileName));
fileContent = filePart.getInputStream();
int read = 0;
final byte[] bytes = new byte[1024];
while ((read = fileContent.read(bytes)) != -1) {
out.write(bytes, 0, read);
}
writer.println("New file " + fileName + " created at " + path);
} catch (FileNotFoundException fne) {
writer.println("You either did not specify a file to upload or are "
+ "trying to upload a file to a protected or nonexistent "
+ "location.");
writer.println("<br/> ERROR: " + fne.getMessage());
} finally {
if (out != null) {
out.close();
}
if (fileContent != null) {
fileContent.close();
}
if (writer != null) {
writer.close();
}
}
}
Spring MVC method:
@ResponseBody
@RequestMapping(value="/upload/", method=RequestMethod.POST,
produces = "text/plain")
public String uploadFile(MultipartHttpServletRequest request)
throws IOException {
Iterator<String> itr = request.getFileNames();
MultipartFile file = request.getFile(itr.next());
MultiValueMap<String, Object> parts =
new LinkedMultiValueMap<String, Object>();
parts.add("file", new ByteArrayResource(file.getBytes()));
parts.add("filename", file.getOriginalFilename());
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String, Object>> requestEntity =
new HttpEntity<MultiValueMap<String, Object>>(parts, headers);
// file upload path on destination server
parts.add("destination", "./");
ResponseEntity<String> response =
restTemplate.exchange("http://localhost:8080/pi",
HttpMethod.POST, requestEntity, String.class);
if (response != null && !response.getBody().trim().equals("")) {
return response.getBody();
}
return "error";
}
Using these I can succesfully upload a file through the MVC method to the servlet by the following curl:
curl --form [email protected] localhost:8080/upload/
Solution 3
Since version 5.1 the Spring Framework ships with its own Resource
implementation for MultipartFile
s. You can therefore simplify Lorenzo's answer by removing the MultipartInputStreamFileResource
class and filling the map as follows:
[...]
for (MultipartFile file : files) {
if (!file.isEmpty()) {
map.add("images", file.getResource());
}
}
[...]
Mateusz Mańka
Updated on July 09, 2022Comments
-
Mateusz Mańka almost 2 years
I am trying to upload a file with RestTemplate to Raspberry Pi with Jetty. On Pi there is a servlet running:
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { PrintWriter outp = resp.getWriter(); StringBuffer buff = new StringBuffer(); File file1 = (File) req.getAttribute("userfile1"); String p = req.getParameter("path"); boolean success = false; if (file1 == null || !file1.exists()) { buff.append("File does not exist\n"); } else if (file1.isDirectory()) { buff.append("File is a directory\n"); } else { File outputFile = new File(req.getParameter("userfile1")); if(isValidPath(p)){ p = DRIVE_ROOT + p; final File finalDest = new File(p + outputFile.getName()); success = false; try { copyFileUsingFileChannels(file1, finalDest); finalDest.setWritable(true); success = true; } catch (Exception e) { e.printStackTrace(); } if (success){ buff.append("File successfully uploaded.\n"); } else{ buff.append("Failed to save file."); } } else{ buff.append("Invalid path.\n"); } } outp.write(buff.toString()); }
I am able to successfully do it with curl
curl --form userfile1=@/home/pi/src/CreateNewFolderServlet.java --form press=OK localhost:2222/pi/GetFileServlet?path="/media/"
This is the method that is supposed to have the same functionality on webapp.
@ResponseBody @RequestMapping(value="/upload/",method=RequestMethod.POST ,produces = "text/plain") public String uploadFile(MultipartHttpServletRequest request2, HttpServletResponse response2){ Iterator<String> itr = request2.getFileNames(); MultipartFile file = request2.getFile(itr.next()); System.out.println(file.getOriginalFilename() +" uploaded!"); System.out.println(file.toString()); MultiValueMap<String, Object> parts = new LinkedMultiValueMap<String, Object>(); parts.add("userfile1",file); //reqEntity.addPart("userfile1", file); String path="/public/"; RestTemplate restTemplate = new RestTemplate(); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.MULTIPART_FORM_DATA); System.out.println("1"); HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<MultiValueMap<String, Object>>(parts, headers); String url = url2+"/pi/GetFileServlet?path="+path; System.out.println("2"); /* restTemplate.getMessageConverters().add(new FormHttpMessageConverter()); restTemplate.getMessageConverters().add( new MappingJackson2HttpMessageConverter());*/ System.out.println("3"); ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, request,String.class); System.out.println("4"); System.out.println("response : " +response); if(response==null||response.getBody().trim()==""){ return "error"; } return response.getBody(); }
This is the output that I get:
ui-elements.html uploaded!
org.springframework.web.multipart.support.StandardMultipartHttpServletRequest$StandardMultipartFile@47e7673e
1
2
3
As you can see number 4 is not printed No exception in console. Exceptions found during debugging:
org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON: No serializer found for class java.io.ByteArrayInputStream and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: org.springframework.web.multipart.support.StandardMultipartFile["inputStream"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: No serializer found for class java.io.ByteArrayInputStream and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: org.springframework.web.multipart.support.StandardMultipartFile["inputStream"])
-
chrismarx over 8 yearsfor spring 3.1 & 3.2 at least, I also needed to deal with the bug in resttemplate when sending byte arrays - stackoverflow.com/questions/4118670/…
-
Tharsan Sivakumar over 7 yearsYou saved me at last. Yes Spring MVC perfectly working for me
-
Manish Singh over 6 yearsSurprisingly, the accepted answer didn't work for me, but this one worked! Thanks.
-
Krish over 6 years@lorenzo-polidori Can you please provide example controller method on how to receive MultipartInputStreamFileResource ? i.e., sample controller method for InputStreamResource
-
Brent Bradburn almost 6 yearsI have used this excellent approach, with some improvements (including further reduced memory consumption) -- and posted a followup here: Streaming upload via @Bean-provided RestTemplateBuilder buffers full file.
-
Deep Lathia about 5 yearsI tried this method but did not work for me. I am facing an issue making a POST request with the data in multipart format. Here's my question if you could guide me with a solution stackoverflow.com/questions/54429549/…
-
Deep Lathia about 5 yearsI tried this method but did not work for me. I am facing an issue making a POST request with the data in multipart format. Here's my question if you could guide me with a solution stackoverflow.com/questions/54429549/…
-
RobOhRob about 5 yearsYou're a genius. I couldn't get anything to work but this solution did the trick.
-
hzpz over 4 yearsSpring ships with its own
MultipartFileResource
now. See my answer for the details. -
Ravindra Ranwala about 3 yearsExactly, that worked perfectly for me. You saved my day. Thanks !