Spring, Java : Streaming file download for avoiding out of memory errors
<p>I am working on a Spring-MVC application in which users are able to download files. The users can click on an attachment which triggers a download mechanism. </p>

<p>Yesterday, when multiple downloads and two of which had approximately 2 GB files, it caused an out of memory error(log below). </p>

<p>To avoid this problem, one way to solve this problem seemed like streaming the download data in chunks, and only processing those chunks in Service layer, rather than the entire file. </p>

<p>Unfortunately, I don't know how to move ahead with this, any help would be nice. If this option can't fly, any recommendations on how to solve this problem.</p>

<p>Error log :</p>

<pre><code>HTTP Status 500 - Handler processing failed; nested exception is java.lang.OutOfMemoryError: Direct buffer memory

type Exception report

message Handler processing failed; nested exception is java.lang.OutOfMemoryError: Direct buffer memory

description The server encountered an internal error that prevented it from fulfilling this request.

exception

org.springframework.web.util.NestedServletException: Handler processing failed; nested exception is java.lang.OutOfMemoryError: Direct buffer memory
    org.springframework.web.servlet.DispatcherServlet.triggerAfterCompletionWithError(DispatcherServlet.java:1303)
    org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:977)
    org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893)
    org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:967)
    org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:858)
    javax.servlet.http.HttpServlet.service(HttpServlet.java:620)
    org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:843)
</code></pre>

<p>Controller code :</p>

<pre><code>    @RequestMapping(value = "/download/attachment/{attachid}", method = RequestMethod.GET)
        public void getAttachmentFromDatabase(@PathVariable("attachid") int attachid,
    , HttpServletResponse response,) {

response.setContentType("application/octet-stream");
GroupAttachments groupAttachments = this.groupAttachmentsService.getAttachmenById(attachid);
response.setHeader("Content-Disposition", "attachment; filename=\"" + groupAttachments.getFileName() + "\"");
                            response.setContentLength(groupAttachments.getSendAttachment().length);
                            FileCopyUtils.copy(groupAttachments.getSendAttachment(), response.getOutputStream());
    response.flushBuffer();

    }
</code></pre>

<p>Service layer :</p>

<pre><code>@Override
    public GroupAttachments getAttachmenById(int attachId) {
        Person person = this.personService.getCurrentlyAuthenticatedUser();
        GroupAttachments groupAttachments = this.groupAttachmentsDAO.getAttachmenById(attachId);

        GroupMembers groupMembers = this.groupMembersService.returnMembersMatchingUsernameAccountId(person.getUsername(),
                groupAttachments.getGroupId());
        if (!(groupMembers == null)) {
            if (person.getUsername().equals(groupMembers.getMemberUsername())) {
                try {
                    Path path = Paths.get(msg + groupAttachments.getGroupId() + "/" +
                            groupAttachments.getFileIdentifier());
                    groupAttachments.setSendAttachment(Files.readAllBytes(path));
                    return groupAttachments;
                } catch (IOException ignored) {
                    this.groupAttachmentsDAO.removeAttachment(attachId);
                    return null;
                }
            }
            return null;
        } else {
            return null;
        }
    }
</code></pre>

<p>Thank you. :-)</p>

<p><em>Update</em> </p>

<p>New Download mechanism :</p>

<p>Controller : </p>

<pre><code> public ResponseEntity&lt;byte[]&gt; getAttachmentFromDatabase(@PathVariable("attachid") int attachid,
                                                    @PathVariable("groupaccountid") Long groupAccountId, @PathVariable("api") String api,
                                                    HttpServletResponse response,
                                                    @PathVariable("type") boolean type) {

 Path path = this.groupAttachmentsService.getAttachmentPathById(attachid);

        GroupAttachments groupAttachments = this.groupAttachmentsService.getAttachmentObjectOnlyById(attachid);
                        response.setContentType("application/octet-stream");
                        response.setHeader("Content-Disposition", "attachment; filename=\""+groupAttachments.getFileName()+"\"");
  try {
OutputStream outputStream = response.getOutputStream();

Files.copy(path,outputStream);
outputStream.flush();
outputStream.close();
response.flushBuffer();
}
</code></pre>

<p>Service layer :</p>

<pre><code>@Override
    public Path getAttachmentPathById(int attachId){
        Person person = this.personService.getCurrentlyAuthenticatedUser();
        GroupAttachments groupAttachments = this.groupAttachmentsDAO.getAttachmenById(attachId);

        GroupMembers groupMembers = this.groupMembersService.returnMembersMatchingUsernameAccountId(person.getUsername(),
                groupAttachments.getGroupId());
        if (!(groupMembers == null)) {
            if (person.getUsername().equals(groupMembers.getMemberUsername())) {
                try {
                    return Paths.get(msg + groupAttachments.getGroupId() + "/" +
                            groupAttachments.getFileIdentifier());
                } catch (Exception ignored) {
                    return null;
                }
            }
            return null;
        } else {
            return null;
        }
    }
</code></pre>