Downloading a file from spring controllers

JavaSpringFileDownloadController

Java Problem Overview


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 freemarker and a PDF generation framework like iText. Any better way?

However, my main problem is how do I allow the user to download a file through a Spring Controller?

Java Solutions


Solution 1 - Java

@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");

Solution 2 - Java

I 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/{file_name}", method = RequestMethod.GET)
@ResponseBody
public FileSystemResource getFile(@PathVariable("file_name") String fileName) {
	return new FileSystemResource(myService.getFileFor(fileName)); 
}

Solution 3 - Java

You 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.

Solution 4 - Java

With 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/{fileName}", method = RequestMethod.GET)
public HttpEntity<byte[]> 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<byte[]>(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/{fileName}", method = RequestMethod.GET)
public HttpEntity<byte[]> 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<byte[]>(new FileSystemResource(document),
                                  header);
}

Solution 5 - Java

If you:

  • Don't want to load the whole file into a byte[] before sending to the response;
  • Want/need to send/download it via InputStream;
  • Want to have full control of the Mime Type and file name sent;
  • Have other @ControllerAdvice picking up exceptions for you (or not).

The code below is what you need:

@RequestMapping(value = "/stuff/{stuffId}", method = RequestMethod.GET)
public ResponseEntity<FileSystemResource> 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<FileSystemResource>(
        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.



InputStreamResource

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<InputStreamResource>(isr, respHeaders, HttpStatus.OK);

Solution 6 - Java

Do not do anything by hand, delegate the work to the framework instead:

  1. Return ResponseEntity<Resource> from a handler method
  2. Specify Content-Type explicitly
  3. Set Content-Disposition if necessary:
    1. filename
    2. type
      1. inline to force preview in a browser
      2. attachment to force a download
@Controller
public class DownloadController {
    @GetMapping("/downloadPdf.pdf")
    // 1.
    public ResponseEntity<Resource> downloadPdf() {
        FileSystemResource resource = new FileSystemResource("/home/caco3/Downloads/JMC_Tutorial.pdf");
        // 2.
        MediaType mediaType = MediaTypeFactory
                .getMediaType(resource)
                .orElse(MediaType.APPLICATION_OCTET_STREAM);
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(mediaType);
        // 3
        ContentDisposition disposition = ContentDisposition
                // 3.2
                .inline() // or .attachment()
                // 3.1
                .filename(resource.getFilename())
                .build();
        headers.setContentDisposition(disposition);
        return new ResponseEntity<>(resource, headers, HttpStatus.OK);
    }
}
Explanation

Return ResponseEntity<Resource>

When you return a ResponseEntity<Resource>, the ResourceHttpMessageConverter kicks in and writes an appropriate response.

The resource could be:

If you need to download a file from application resources directory, check out my answer: it explains how to locate the resource in class path using ClassPathResource

Be aware of possibly wrong Content-Type header set (see FileSystemResource is returned with content type json). That's why this answer suggests setting the Content-Type explicitly.

Specify Content-Type explicitly:

Some options are:

The MediaTypeFactory allows to discover the MediaType appropriate for the Resource (see also /org/springframework/http/mime.types file)

Set Content-Disposition if necessary:

Sometimes it is necessary to force a download in a browser or to make the browser open a file as a preview. You can use the Content-Disposition header to satisfy this requirement:

> The first parameter in the HTTP context is either inline (default value, indicating it can be displayed inside the Web page, or as the Web page) or attachment (indicating it should be downloaded; most browsers presenting a 'Save as' dialog, prefilled with the value of the filename parameters if present).

In the Spring Framework a ContentDisposition can be used.

To preview a file in a browser:

ContentDisposition disposition = ContentDisposition
        .builder("inline") // Or .inline() if you're on Spring MVC 5.3+
        .filename(resource.getFilename())
        .build();

To force a download:

ContentDisposition disposition = ContentDisposition
        .builder("attachment") // Or .attachment() if you're on Spring MVC 5.3+
        .filename(resource.getFilename())
        .build();

Use InputStreamResource carefully:

Since an InputStream can be read only once, Spring won't write Content-Length header if you return an InputStreamResource (here is a snippet of code from ResourceHttpMessageConverter):

@Override
protected Long getContentLength(Resource resource, @Nullable MediaType contentType) throws IOException {
	// Don't try to determine contentLength on InputStreamResource - cannot be read afterwards...
	// Note: custom InputStreamResource subclasses could provide a pre-calculated content length!
	if (InputStreamResource.class == resource.getClass()) {
		return null;
	}
	long contentLength = resource.contentLength();
	return (contentLength < 0 ? null : contentLength);
}

In other cases it works fine:

~ $ curl -I localhost:8080/downloadPdf.pdf  | grep "Content-Length"
Content-Length: 7554270

Solution 7 - Java

This code is working fine to download a file automatically from spring controller on clicking a link on jsp.

@RequestMapping(value="/downloadLogFile")
public void getLogFile(HttpSession session,HttpServletResponse response) throws Exception {
    try {
        String filePathToBeServed = //complete file name with path;
        File fileToDownload = new File(filePathToBeServed);
        InputStream inputStream = new FileInputStream(fileToDownload);
        response.setContentType("application/force-download");
        response.setHeader("Content-Disposition", "attachment; filename="+fileName+".txt"); 
        IOUtils.copy(inputStream, response.getOutputStream());
        response.flushBuffer();
        inputStream.close();
    } catch (Exception e){
        LOGGER.debug("Request could not be completed at this moment. Please try again.");
        e.printStackTrace();
    }
    
}

Solution 8 - Java

Below code worked for me to generate and download a text file.

@RequestMapping(value = "/download", method = RequestMethod.GET)
public ResponseEntity<byte[]> getDownloadData() throws Exception {
		
	String regData = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.";
	byte[] output = regData.getBytes();

	HttpHeaders responseHeaders = new HttpHeaders();
	responseHeaders.set("charset", "utf-8");
	responseHeaders.setContentType(MediaType.valueOf("text/html"));
	responseHeaders.setContentLength(output.length);
	responseHeaders.set("Content-disposition", "attachment; filename=filename.txt");

	return new ResponseEntity<byte[]>(output, responseHeaders, HttpStatus.OK);
}

Solution 9 - Java

What I can quickly think of is, generate the pdf and store it in webapp/downloads/< RANDOM-FILENAME>.pdf from the code and send a forward to this file using HttpServletRequest

request.getRequestDispatcher("/downloads/<RANDOM-FILENAME>.pdf").forward(request, response);

or if you can configure your view resolver something like,

  <bean id="pdfViewResolver"
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="viewClass"
              value="org.springframework.web.servlet.view.JstlView" />
    <property name="order" value=”2″/>
    <property name="prefix" value="/downloads/" />
    <property name="suffix" value=".pdf" />
  </bean>

then just return

return "RANDOM-FILENAME";

Solution 10 - Java

The following solution work for me

    @RequestMapping(value="/download")
	public void getLogFile(HttpSession session,HttpServletResponse response) throws Exception {
	    try {
	    	
	    	String fileName="archivo demo.pdf";
	        String filePathToBeServed = "C:\\software\\Tomcat 7.0\\tmpFiles\\";
	        File fileToDownload = new File(filePathToBeServed+fileName);
	        
	        InputStream inputStream = new FileInputStream(fileToDownload);
	        response.setContentType("application/force-download");
	        response.setHeader("Content-Disposition", "attachment; filename="+fileName); 
	        IOUtils.copy(inputStream, response.getOutputStream());
	        response.flushBuffer();
	        inputStream.close();
	    } catch (Exception exception){
	        System.out.println(exception.getMessage());
	    }

	}

Solution 11 - Java

something like below

@RequestMapping(value = "/download", method = RequestMethod.GET)
public void getFile(HttpServletResponse response) {
    try {
        DefaultResourceLoader loader = new DefaultResourceLoader();
        InputStream is = loader.getResource("classpath:META-INF/resources/Accepted.pdf").getInputStream();
        IOUtils.copy(is, response.getOutputStream());
        response.setHeader("Content-Disposition", "attachment; filename=Accepted.pdf");
        response.flushBuffer();
    } catch (IOException ex) {
        throw new RuntimeException("IOError writing file to output stream");
    }
}

You can display PDF or download it examples here

Solution 12 - Java

If it helps anyone. You can do what the accepted answer by Infeligo has suggested but just put this extra bit in the code for a forced download.

response.setContentType("application/force-download");

Solution 13 - Java

In my case I'm generating some file on demand, so also url has to be generated.

For me works something like that:

@RequestMapping(value = "/files/{filename:.+}", method = RequestMethod.GET, produces = "text/csv")
@ResponseBody
public FileSystemResource getFile(@PathVariable String filename) {
	String path = dataProvider.getFullPath(filename);
	return new FileSystemResource(new File(path));
}

Very important is mime type in produces and also that, that name of the file is a part of the link so you has to use @PathVariable.

HTML code looks like that:

<a th:href="@{|/dbreport/files/${file_name}|}">Download</a>

Where ${file_name} is generated by Thymeleaf in controller and is i.e.: result_20200225.csv, so that whole url behing link is: example.com/aplication/dbreport/files/result_20200225.csv.

After clicking on link browser asks me what to do with file - save or open.

Solution 14 - Java

This can be a useful answer.

https://stackoverflow.com/questions/60034234/is-it-ok-to-export-data-as-pdf-format-in-frontend/

Extending to this, adding content-disposition as an attachment(default) will download the file. If you want to view it, you need to set it to inline.

Solution 15 - Java

I had to add this to download any file

    response.setContentType("application/octet-stream");
    response.setHeader("Content-Disposition",
            "attachment;filename="+"file.txt");

all code:

@Controller
public class FileController {

@RequestMapping(value = "/file", method =RequestMethod.GET)
@ResponseBody
public FileSystemResource getFile(HttpServletResponse response) {

    final File file = new File("file.txt");
    response.setContentType("application/octet-stream");
    response.setHeader("Content-Disposition",
            "attachment;filename="+"file.txt");
    return new FileSystemResource(file);
 }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMilindaDView Question on Stackoverflow
Solution 1 - JavaInfeligoView Answer on Stackoverflow
Solution 2 - JavaScott CarlsonView Answer on Stackoverflow
Solution 3 - Javalobster1234View Answer on Stackoverflow
Solution 4 - JavaRalphView Answer on Stackoverflow
Solution 5 - JavaacdcjuniorView Answer on Stackoverflow
Solution 6 - JavaDenisView Answer on Stackoverflow
Solution 7 - JavaSunilView Answer on Stackoverflow
Solution 8 - JavaSiva KumarView Answer on Stackoverflow
Solution 9 - JavakalyanView Answer on Stackoverflow
Solution 10 - JavaJorge Santos NeillView Answer on Stackoverflow
Solution 11 - JavaxxyView Answer on Stackoverflow
Solution 12 - JavaSagar NairView Answer on Stackoverflow
Solution 13 - JavaTomasz DzięcielewskiView Answer on Stackoverflow
Solution 14 - JavatheNextBigThingView Answer on Stackoverflow
Solution 15 - JavaInanc CakilView Answer on Stackoverflow