I have a requirement where I need to download a PDF from the website. The PDF needs to be generated within the code, which I thought would be a combination of a freemarker and a PDF generation framework like iText . Is there any better way? However, my main problem is how do I allow the users to download the file through a Spring Controller?
2,373 5 5 gold badges 30 30 silver badges 57 57 bronze badges asked Apr 15, 2011 at 6:46 7,643 10 10 gold badges 45 45 silver badges 63 63 bronze badgesIt worth mentioning that Spring Framework changed a lot since 2011, so you can do it in a reactive way as well - here is an example
Commented Aug 9, 2019 at 7:32With later versions of spring, you just need to return the byte array with appropriate headers in ResponseEntity. Here is a full example: allaboutspringframework.com/…
Commented Mar 29, 2021 at 13:36@RequestMapping(value = "/files/", 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");
14k 6 6 gold badges 61 61 silver badges 87 87 bronze badges
answered Apr 15, 2011 at 6:59
11.8k 9 9 gold badges 39 39 silver badges 51 51 bronze badges
This is pretty much what I was about to say, but you should probably also set the response type header to something appropriate for the file.
Commented Apr 15, 2011 at 7:01Yep, just edited the post. I had various file types generated, so I left it to the browser to determine the content type of the file by its extension.
Commented Apr 15, 2011 at 7:05 Any particular reason to use Apache's IOUtils instead of Spring's FileCopyUtils ? Commented Sep 18, 2012 at 16:12 Here is a better solution: stackoverflow.com/questions/16652760/… Commented Jul 14, 2015 at 15:16@Powerlord Spring method closes the streams, Apache one does not. There are debates if the Servlet response output stream should be closed in the Controller code or by the Servlet container .
Commented Mar 7, 2018 at 21:04I was able to stream line this by using the built in support in Spring with it's ResourceHttpMessageConverter. This will set the content-length and content-type if it can determine the mime-type
@RequestMapping(value = "/files/", method = RequestMethod.GET) @ResponseBody public FileSystemResource getFile(@PathVariable("file_name") String fileName)answered May 31, 2012 at 15:53 Scott Carlson Scott Carlson 3,804 1 1 gold badge 18 18 silver badges 11 11 bronze badges
This works. But the file (.csv file) is displayed in the browser and not downloaded - how can I force the browser to download?
Commented Jul 12, 2013 at 8:26You can add produces = MediaType.APPLICATION_OCTET_STREAM_VALUE to the @RequestMapping to force download
Commented Sep 25, 2013 at 17:33 Also You should add ) Commented Nov 29, 2013 at 12:15 Is there a way to set the Content-Disposition header with this way? Commented Apr 15, 2015 at 12:54I didn't have a need for that, but I think you could add HttpResponse as a parameter to the method, and then "response.setHeader("Content-Disposition", "attachment; filename=somefile.pdf");"
Commented Apr 16, 2015 at 13:42You should be able to write the file on the response directly. Something like
response.setContentType("application/pdf"); response.setHeader("Content-Disposition", "attachment; filename=\"somefile.pdf\"");
and then write the file as a binary stream on response.getOutputStream() . Remember to do response.flush() at the end and that should do it.
14.4k 15 15 gold badges 99 99 silver badges 135 135 bronze badges answered Apr 15, 2011 at 7:01 lobster1234 lobster1234 7,749 28 28 silver badges 30 30 bronze badgesisn't the 'Spring' way to set the content type like this? @RequestMapping(value = "/foo/bar", produces = "application/pdf")
Commented May 7, 2014 at 5:44@Francis what if your application downloads different file types? Lobster1234's answer enables you to dynamically set the content disposition.
Commented Nov 13, 2015 at 2:29that's true @Rose, but I believe it would be better practice to define different end-points per format
Commented Nov 13, 2015 at 3:10I guess not, because it's not scalable. We are currently supporting a dozen types of resources. We might support more file types based on what users want to upload in that case we might end up with so many end points essentially doing the same thing. IMHO there has to be only one download end point and it handles multitude of file types. @Francis
Commented Nov 13, 2015 at 5:12 it's absolutely "scalable", but we can agree to disagree whether it's the best practice Commented Nov 16, 2015 at 22:42With Spring 3.0 you can use the HttpEntity return object. If you use this, then your controller does not need a HttpServletResponse object, and therefore it is easier to test. Except this, this answer is relative equals to the one of Infeligo.
If the return value of your pdf framework is an byte array (read the second part of my answer for other return values) :
@RequestMapping(value = "/files/", method = RequestMethod.GET) public HttpEntity createPdf( @PathVariable("fileName") String fileName) throws IOException < byte[] documentBody = this.pdfFramework.createPdf(filename); HttpHeaders header = new HttpHeaders(); header.setContentType(MediaType.APPLICATION_PDF); header.set(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + fileName.replace(" ", "_")); header.setContentLength(documentBody.length); return new HttpEntity(documentBody, header); >
If the return type of your PDF Framework ( documentBbody ) is not already a byte array (and also no ByteArrayInputStream ) then it would been wise NOT to make it a byte array first. Instead it is better to use:
example with FileSystemResource :
@RequestMapping(value = "/files/", method = RequestMethod.GET) public HttpEntity createPdf( @PathVariable("fileName") String fileName) throws IOException < File document = this.pdfFramework.createPdf(filename); HttpHeaders header = new HttpHeaders(); header.setContentType(MediaType.APPLICATION_PDF); header.set(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + fileName.replace(" ", "_")); header.setContentLength(document.length()); return new HttpEntity(new FileSystemResource(document), header); >
1,506 2 2 gold badges 19 19 silver badges 36 36 bronze badges
answered Oct 29, 2012 at 10:46
120k 57 57 gold badges 294 294 silver badges 390 390 bronze badges
-1 this will un-neccessarily load the whole file in memory can easily casue OutOfMemoryErrors.
Commented Mar 7, 2014 at 12:39
@FaisalFeroz: yes this is right, but the file document is anyway created in memory (see the question: "PDF needs to be generated within the code"). Anyway - what is your solution that overcome this problem?
Commented Mar 7, 2014 at 17:30You may also use ResponseEntity which is a super of HttpEntity which allows you to specify the response http status code. Example: return new ResponseEntity
@Amr Mostafa: ResponseEntity is a subclass of HttpEntity (but I get it) on the other hand 201 CREATED is not what I would use when I return just an view to the data. (see w3.org/Protocols/rfc2616/rfc2616-sec10.html for 201 CREATED)
Commented Oct 8, 2014 at 12:39Is there a reason why you are replacing whitespaces with underscore in the filename? You can wrap it in quotes to send the actual name.
Commented Oct 22, 2018 at 13:15The code below is what you need:
@RequestMapping(value = "/stuff/", method = RequestMethod.GET) public ResponseEntity downloadStuff(@PathVariable int stuffId) throws IOException < String fullPath = stuffService.figureOutFileNameFor(stuffId); File file = new File(fullPath); long fileLength = file.length(); // this is ok, but see note below HttpHeaders respHeaders = new HttpHeaders(); respHeaders.setContentType("application/pdf"); respHeaders.setContentLength(fileLength); respHeaders.setContentDispositionFormData("attachment", "fileNameIwant.pdf"); return new ResponseEntity( new FileSystemResource(file), respHeaders, HttpStatus.OK ); >
More on setContentLength() : First of all, the content-length header is optional per the HTTP 1.1 RFC. Still, if you can provide a value, it is better. To obtain such value, know that File#length() should be good enough in the general case, so it is a safe default choice.
In very specific scenarios, though, it can be slow, in which case you should have it stored previously (e.g. in the DB), not calculated on the fly. Slow scenarios include: if the file is very large, specially if it is on a remote system or something more elaborated like that - a database, maybe.
If your resource is not a file, e.g. you pick the data up from the DB, you should use InputStreamResource . Example:
InputStreamResource isr = new InputStreamResource(. ); return new ResponseEntity(isr, respHeaders, HttpStatus.OK);