Do not do anything by hand, delegate the work to the framework instead:
- Return
ResponseEntity<Resource> from a handler method
- Specify
Content-Type explicitly
- Set
Content-Disposition if necessary:
- filename
- type
inline to force preview in a browser
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:
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