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
ResponseEntity from a handler methodContent-Type explicitlyContent-Disposition if necessary:
@Controller
public class DownloadController {
@GetMapping("/downloadPdf.pdf")
// 1.
public ResponseEntity 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);
}
}
Return ResponseEntity
When you return a ResponseEntity
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) orattachment(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