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