10

How to provide large files for download through spring controller ? I followed few discussions on similar topic :

Downloading a file from spring controllers

but those solutions fails for large files ~ 300mb - 600mb. I am getting OutOfMemoryException on the last line :

@RequestMapping(value = "/file/{dummyparam}.pdf", method = RequestMethod.GET, produces=MediaType.APPLICATION_OCTET_STREAM_VALUE)
public @ResponseBody byte[] getFile(@PathVariable("dummyparam") String dummyparam, HttpServletResponse response) {
.
.       
InputStream is = new FileInputStream(resultFile);

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

My (naive) assumption was that IOUtils will handle even large files but this is not obviously happening. Is there any way how to split file into chunks as download is in progress ? Files are usually around 300 - 600mb large. Max number of concurrent downloads is estimated to 10.

Easy way would be to link files as static content in the webserver directory but we would like to try do it in within our Spring app.

Community
  • 1
  • 1
Martin V.
  • 3,560
  • 6
  • 31
  • 47

2 Answers2

22

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(); 
}
Arun P Johny
  • 384,651
  • 66
  • 527
  • 531
  • 1
    P what should be the size of BYTES_DOWNLOAD in the above implementation – sam Mar 08 '19 at 15:14
  • Use instead, `IOUtils.copyLarge(inputStream, response.getOutputStream());` where buffer size is given by DEFAULT_BUFFER_SIZE. Size may vary from 4KB to 8KB based on the jar version. – Jack Mar 03 '21 at 01:31
1

For Spring , Need use InputStreamResource class in ResponseEntity .

Demo Code :

        MediaType mediaType = MediaTypeUtils.getMediaTypeForFileName(this.servletContext, fileName);
        System.out.println("fileName: " + fileName);
        System.out.println("mediaType: " + mediaType);

        File file = new File(DIRECTORY + "/" + fileName);
        InputStreamResource resource = new InputStreamResource(new FileInputStream(file));

        return ResponseEntity.ok()
                // Content-Disposition
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + file.getName())
                // Content-Type
                .contentType(mediaType)
                // Contet-Length
                .contentLength(file.length()) //
                .body(resource);
    }

Ref Link : https://o7planning.org/en/11765/spring-boot-file-download-example

Kamlesh
  • 517
  • 3
  • 10
  • 28
  • **InputStreamResource** is one of the most generic implementations of Spring's Resource interface and made only for wrapping already opened streams, the process of which creation we cannot control. I'd rather consider using **ClassPathResource** or **FileSystemResource** depending on the file location in the file system. – Nikita Kobtsev Mar 19 '22 at 22:56