Download Large File from Server Using Rest Template Java Spring MVC

Download Large file from server using REST template Java Spring MVC

Here is how I do it. Based on hints from this Spring Jira issue.

RestTemplate restTemplate // = ...;

// Optional Accept header
RequestCallback requestCallback = request -> request.getHeaders()
.setAccept(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL));

// Streams the response instead of loading it all in memory
ResponseExtractor<Void> responseExtractor = response -> {
// Here I write the response to a file but do what you like
Path path = Paths.get("some/path");
Files.copy(response.getBody(), path);
return null;
};
restTemplate.execute(URI.create("www.something.com"), HttpMethod.GET, requestCallback, responseExtractor);

From the aforementioned Jira issue:

Note that you cannot simply return the InputStream from the extractor, because by the time the execute method returns, the underlying connection and stream are already closed.

Update for Spring 5

Spring 5 introduced the WebClient class which allows asynchronous (e.g. non-blocking) http requests. From the doc:

By comparison to the RestTemplate, the WebClient is:

  • non-blocking, reactive, and supports higher concurrency with less hardware resources.
  • provides a functional API that takes advantage of Java 8 lambdas.
  • supports both synchronous and asynchronous scenarios.
  • supports streaming up or down from a server.

To get WebClient in Spring Boot, you need this dependency:

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

For the moment, I'm sticking with RestTemplate because I don't want to pull in another dependency only to get access to WebClient.

Spring MVC : large files for download, OutOfMemoryException

It is because you are reading the entire file into memory, use a buffered read and write instead.

@RequestMapping(value = "/file/{dummyparam}.pdf", method = RequestMethod.GET, produces=MediaType.APPLICATION_OCTET_STREAM_VALUE)
public void getFile(@PathVariable("dummyparam") String dummyparam, HttpServletResponse response) {

InputStream is = new FileInputStream(resultFile);

response.setHeader("Content-Disposition", "attachment; filename=\"dummyname " + dummyparam + ".pdf\"");

int read=0;
byte[] bytes = new byte[BYTES_DOWNLOAD];
OutputStream os = response.getOutputStream();

while((read = is.read(bytes))!= -1){
os.write(bytes, 0, read);
}
os.flush();
os.close();
}

Downloading huge files from spring boot REST service

The code of the RestController is working as expected with a small memory footprint (the file is never loaded to the main memory completely).

The client code that works for me (also with a small memory footprint) can be found here.

download a file from Spring boot rest service

Option 1 using an InputStreamResource

Resource implementation for a given InputStream.

Should only be used if no other specific Resource implementation is > applicable. In particular, prefer ByteArrayResource or any of the file-based Resource implementations where possible.

@RequestMapping(path = "/download", method = RequestMethod.GET)
public ResponseEntity<Resource> download(String param) throws IOException {

// ...

InputStreamResource resource = new InputStreamResource(new FileInputStream(file));

return ResponseEntity.ok()
.headers(headers)
.contentLength(file.length())
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(resource);
}

Option2 as the documentation of the InputStreamResource suggests - using a ByteArrayResource:

@RequestMapping(path = "/download", method = RequestMethod.GET)
public ResponseEntity<Resource> download(String param) throws IOException {

// ...

Path path = Paths.get(file.getAbsolutePath());
ByteArrayResource resource = new ByteArrayResource(Files.readAllBytes(path));

return ResponseEntity.ok()
.headers(headers)
.contentLength(file.length())
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(resource);
}

Downloading a file from spring controllers

@RequestMapping(value = "/files/{file_name}", method = RequestMethod.GET)
public void getFile(
@PathVariable("file_name") String fileName,
HttpServletResponse response) {
try {
// get your file as InputStream
InputStream is = ...;
// copy it to response's OutputStream
org.apache.commons.io.IOUtils.copy(is, response.getOutputStream());
response.flushBuffer();
} catch (IOException ex) {
log.info("Error writing file to output stream. Filename was '{}'", fileName, ex);
throw new RuntimeException("IOError writing file to output stream");
}

}

Generally speaking, when you have response.getOutputStream(), you can write anything there. You can pass this output stream as a place to put generated PDF to your generator. Also, if you know what file type you are sending, you can set

response.setContentType("application/pdf");


Related Topics



Leave a reply



Submit