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