Do
- Return
ResponseEntity<Resource> from a handler method
- Specify
Content-Type
- Set
Content-Disposition if necessary:
- filename
- type
inline to force preview in a browser
attachment to force a download
Example
@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 writes file contents
Examples of Resource implementations:
Specify Content-Type explicitly:
Reason: see "FileSystemResource is returned with content type json" question
Options:
- Hardcode the header
- Use the
MediaTypeFactory from Spring. The MediaTypeFactory maps Resource to MediaType using the /org/springframework/http/mime.types file
- Use a third party library like Apache Tika
Set Content-Disposition if necessary:
About Content-Disposition header:
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).
Use ContentDisposition in application:
To preview a file in a browser:
ContentDisposition disposition = ContentDisposition
.inline()
.filename(resource.getFilename())
.build();
To force a download:
ContentDisposition disposition = ContentDisposition
.attachment()
.filename(resource.getFilename())
.build();
Use InputStreamResource carefully:
Specify Content-Length using the HttpHeaders#setContentLength method if:
- The length is known
- You use
InputStreamResource
Reason: Spring won't write Content-Length for InputStreamResource because Spring can't determine the length of the resource. 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 Spring sets the Content-Length:
~ $ curl -I localhost:8080/downloadPdf.pdf | grep "Content-Length"
Content-Length: 7554270