I came across a helpful PDF generation code to show the file to the client in a Spring MVC application ("Return generated PDF using Spring MVC"):
#RequestMapping(value = "/form/pdf", produces = "application/pdf")
public ResponseEntity<byte[]> showPdf(DomainModel domain, ModelMap model) {
createPdf(domain, model);
Path path = Paths.get(PATH_FILE);
byte[] pdfContents = null;
try {
pdfContents = Files.readAllBytes(path);
} catch (IOException e) {
e.printStackTrace();
}
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType("application/pdf"));
String filename = NAME_PDF;
headers.setContentDispositionFormData(filename, filename);
headers.setCacheControl("must-revalidate, post-check=0, pre-check=0");
ResponseEntity<byte[]> response = new ResponseEntity<byte[]>(
pdfContents, headers, HttpStatus.OK);
return response;
}
I added a declaration that the method returns a PDF file ("Spring 3.0 Java REST return PDF document"): produces = "application/pdf".
My problem is that when the code above is executed, it immediately asks the client to save the PDF file. I want the PDF file to be viewed first in the browser so that the client can decide whether to save it or not.
I found "How to get PDF content (served from a Spring MVC controller method) to appear in a new window" that suggests to add target="_blank" in the Spring form tag. I tested it and as expected, it showed a new tab but the save prompt appeared again.
Another is "I can't open a .pdf in my browser by Java"'s method to add httpServletResponse.setHeader("Content-Disposition", "inline"); but I don't use HttpServletRequest to serve my PDF file.
How can I open the PDF file in a new tab given my code/situation?
Try
httpServletResponse.setHeader("Content-Disposition", "inline");
But using the responseEntity as follows.
HttpHeaders headers = new HttpHeaders();
headers.add("content-disposition", "attachment; filename=" + fileName)
ResponseEntity<byte[]> response = new ResponseEntity<byte[]>(
pdfContents, headers, HttpStatus.OK);
It should work
Not sure about this, but it seems you are using bad the setContentDispositionFormData, try>
headers.setContentDispositionFormData("attachment", fileName);
Let me know if that works
UPDATE
This behavior depends on the browser and the file you are trying to
serve. With inline, the browser will try to open the file within the
browser.
headers.setContentDispositionFormData("inline", fileName);
Or
headers.add("content-disposition", "inline;filename=" + fileName)
Read this to know difference between inline and attachment
SpringBoot 2
Use this code to display the pdf in the browser.
(PDF in directory resources -> CLASSPATH)
Using ResponseEntity<?>
#GetMapping(value = "/showPDF")
public ResponseEntity<?> exportarPDF( ){
InputStreamResource file = new InputStreamResource(service.exportPDF());
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "inline;attachment; filename=ayuda.pdf")
.contentType(MediaType.APPLICATION_PDF)
.body(file);
}
#Service
public class AyudaServiceImpl implements AyudaService {
private static final Logger LOGGER = LoggerFactory.getLogger(LoadPDF.class);
LoadPDF pdf = new LoadPDF();
public InputStream exportPDF() {
LOGGER.info("Inicia metodo de negocio :: getPDF");
return pdf.getPDF();
}
}
---CLASE
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ResourceUtils;
public class LoadPDF {
private static final Logger LOGGER = LoggerFactory.getLogger(LoadPDF.class);
public InputStream getPDF(){
try {
File file = ResourceUtils.getFile("classpath:ayuda.pdf");
LOGGER.debug("Ruta del archivo pdf: [" + file + "]");
InputStream in = new FileInputStream(file);
LOGGER.info("Encontro el archivo PDF");
return in;
} catch (IOException e) {
LOGGER.error("No encontro el archivo PDF",e.getMessage());
throw new AyudaException("No encontro el archivo PDF", e );
}
}
}
/* Here is a simple code that worked just fine to open pdf(byte stream) file
* in browser , Assuming you have a a method yourService.getPdfContent() that
* returns the bite stream for the pdf file
*/
#GET
#Path("/download/")
#Produces("application/pdf")
public byte[] getDownload() {
byte[] pdfContents = yourService.getPdfContent();
return pdfContents;
}
What happened is that since you "manually" provided headers to the response, Spring did not added the other headers (e.g. produces="application/pdf"). Here's the minimum code to display the pdf inline in the browser using Spring:
#GetMapping(value = "/form/pdf", produces = "application/pdf")
public ResponseEntity<byte[]> showPdf() {
// getPdfBytes() simply returns: byte[]
return ResponseEntity.ok(getPdfBytes());
}
Related
The bounty expires in 4 days. Answers to this question are eligible for a +100 reputation bounty.
Nzall wants to reward an existing answer.
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 a PDF generation framework like iText. Any better way?
However, my main problem is how do I allow the user to download a file through a Spring Controller?
#RequestMapping(value = "/files/{file_name}", method = RequestMethod.GET)
public void getFile(
#PathVariable("file_name") String fileName,
HttpServletResponse response) {
try {
// get your file as InputStream
InputStream is = ...;
// copy it to response's OutputStream
org.apache.commons.io.IOUtils.copy(is, response.getOutputStream());
response.flushBuffer();
} catch (IOException ex) {
log.info("Error writing file to output stream. Filename was '{}'", fileName, ex);
throw new RuntimeException("IOError writing file to output stream");
}
}
Generally speaking, when you have response.getOutputStream(), you can write anything there. You can pass this output stream as a place to put generated PDF to your generator. Also, if you know what file type you are sending, you can set
response.setContentType("application/pdf");
I was able to stream line this by using the built in support in Spring with it's ResourceHttpMessageConverter. This will set the content-length and content-type if it can determine the mime-type
#RequestMapping(value = "/files/{file_name}", method = RequestMethod.GET)
#ResponseBody
public FileSystemResource getFile(#PathVariable("file_name") String fileName) {
return new FileSystemResource(myService.getFileFor(fileName));
}
You should be able to write the file on the response directly. Something like
response.setContentType("application/pdf");
response.setHeader("Content-Disposition", "attachment; filename=\"somefile.pdf\"");
and then write the file as a binary stream on response.getOutputStream(). Remember to do response.flush() at the end and that should do it.
With Spring 3.0 you can use the HttpEntity return object. If you use this, then your controller does not need a HttpServletResponse object, and therefore it is easier to test.
Except this, this answer is relative equals to the one of Infeligo.
If the return value of your pdf framework is an byte array (read the second part of my answer for other return values) :
#RequestMapping(value = "/files/{fileName}", method = RequestMethod.GET)
public HttpEntity<byte[]> createPdf(
#PathVariable("fileName") String fileName) throws IOException {
byte[] documentBody = this.pdfFramework.createPdf(filename);
HttpHeaders header = new HttpHeaders();
header.setContentType(MediaType.APPLICATION_PDF);
header.set(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=" + fileName.replace(" ", "_"));
header.setContentLength(documentBody.length);
return new HttpEntity<byte[]>(documentBody, header);
}
If the return type of your PDF Framework (documentBbody) is not already a byte array (and also no ByteArrayInputStream) then it would been wise NOT to make it a byte array first. Instead it is better to use:
InputStreamResource,
PathResource (since Spring 4.0) or
FileSystemResource,
example with FileSystemResource:
#RequestMapping(value = "/files/{fileName}", method = RequestMethod.GET)
public HttpEntity<byte[]> createPdf(
#PathVariable("fileName") String fileName) throws IOException {
File document = this.pdfFramework.createPdf(filename);
HttpHeaders header = new HttpHeaders();
header.setContentType(MediaType.APPLICATION_PDF);
header.set(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=" + fileName.replace(" ", "_"));
header.setContentLength(document.length());
return new HttpEntity<byte[]>(new FileSystemResource(document),
header);
}
If you:
Don't want to load the whole file into a byte[] before sending to the response;
Want/need to send/download it via InputStream;
Want to have full control of the Mime Type and file name sent;
Have other #ControllerAdvice picking up exceptions for you (or not).
The code below is what you need:
#RequestMapping(value = "/stuff/{stuffId}", method = RequestMethod.GET)
public ResponseEntity<FileSystemResource> downloadStuff(#PathVariable int stuffId)
throws IOException {
String fullPath = stuffService.figureOutFileNameFor(stuffId);
File file = new File(fullPath);
long fileLength = file.length(); // this is ok, but see note below
HttpHeaders respHeaders = new HttpHeaders();
respHeaders.setContentType("application/pdf");
respHeaders.setContentLength(fileLength);
respHeaders.setContentDispositionFormData("attachment", "fileNameIwant.pdf");
return new ResponseEntity<FileSystemResource>(
new FileSystemResource(file), respHeaders, HttpStatus.OK
);
}
More on setContentLength(): First of all, the content-length header is optional per the HTTP 1.1 RFC. Still, if you can provide a value, it is better. To obtain such value, know that File#length() should be good enough in the general case, so it is a safe default choice.
In very specific scenarios, though, it can be slow, in which case you should have it stored previously (e.g. in the DB), not calculated on the fly. Slow scenarios include: if the file is very large, specially if it is on a remote system or something more elaborated like that - a database, maybe.
InputStreamResource
If your resource is not a file, e.g. you pick the data up from the DB, you should use InputStreamResource. Example:
InputStreamResource isr = new InputStreamResource(...);
return new ResponseEntity<InputStreamResource>(isr, respHeaders, HttpStatus.OK);
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:
ByteArrayResource - based in byte[]
FileSystemResource - for a File or a Path
UrlResource - retrieved from java.net.URL
GridFsResource - a blob stored in MongoDB
ClassPathResource - for files in classpath, for example files from resources directory. My answer to question "Read file from resources folder in Spring Boot" explains how to locate the resource in classpath in details
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
This code is working fine to download a file automatically from spring controller on clicking a link on jsp.
#RequestMapping(value="/downloadLogFile")
public void getLogFile(HttpSession session,HttpServletResponse response) throws Exception {
try {
String filePathToBeServed = //complete file name with path;
File fileToDownload = new File(filePathToBeServed);
InputStream inputStream = new FileInputStream(fileToDownload);
response.setContentType("application/force-download");
response.setHeader("Content-Disposition", "attachment; filename="+fileName+".txt");
IOUtils.copy(inputStream, response.getOutputStream());
response.flushBuffer();
inputStream.close();
} catch (Exception e){
LOGGER.debug("Request could not be completed at this moment. Please try again.");
e.printStackTrace();
}
}
Below code worked for me to generate and download a text file.
#RequestMapping(value = "/download", method = RequestMethod.GET)
public ResponseEntity<byte[]> getDownloadData() throws Exception {
String regData = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.";
byte[] output = regData.getBytes();
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.set("charset", "utf-8");
responseHeaders.setContentType(MediaType.valueOf("text/html"));
responseHeaders.setContentLength(output.length);
responseHeaders.set("Content-disposition", "attachment; filename=filename.txt");
return new ResponseEntity<byte[]>(output, responseHeaders, HttpStatus.OK);
}
What I can quickly think of is, generate the pdf and store it in webapp/downloads/< RANDOM-FILENAME>.pdf from the code and send a forward to this file using HttpServletRequest
request.getRequestDispatcher("/downloads/<RANDOM-FILENAME>.pdf").forward(request, response);
or if you can configure your view resolver something like,
<bean id="pdfViewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView" />
<property name="order" value=”2″/>
<property name="prefix" value="/downloads/" />
<property name="suffix" value=".pdf" />
</bean>
then just return
return "RANDOM-FILENAME";
The following solution work for me
#RequestMapping(value="/download")
public void getLogFile(HttpSession session,HttpServletResponse response) throws Exception {
try {
String fileName="archivo demo.pdf";
String filePathToBeServed = "C:\\software\\Tomcat 7.0\\tmpFiles\\";
File fileToDownload = new File(filePathToBeServed+fileName);
InputStream inputStream = new FileInputStream(fileToDownload);
response.setContentType("application/force-download");
response.setHeader("Content-Disposition", "attachment; filename="+fileName);
IOUtils.copy(inputStream, response.getOutputStream());
response.flushBuffer();
inputStream.close();
} catch (Exception exception){
System.out.println(exception.getMessage());
}
}
something like below
#RequestMapping(value = "/download", method = RequestMethod.GET)
public void getFile(HttpServletResponse response) {
try {
DefaultResourceLoader loader = new DefaultResourceLoader();
InputStream is = loader.getResource("classpath:META-INF/resources/Accepted.pdf").getInputStream();
IOUtils.copy(is, response.getOutputStream());
response.setHeader("Content-Disposition", "attachment; filename=Accepted.pdf");
response.flushBuffer();
} catch (IOException ex) {
throw new RuntimeException("IOError writing file to output stream");
}
}
You can display PDF or download it examples here
If it helps anyone. You can do what the accepted answer by Infeligo has suggested but just put this extra bit in the code for a forced download.
response.setContentType("application/force-download");
In my case I'm generating some file on demand, so also url has to be generated.
For me works something like that:
#RequestMapping(value = "/files/{filename:.+}", method = RequestMethod.GET, produces = "text/csv")
#ResponseBody
public FileSystemResource getFile(#PathVariable String filename) {
String path = dataProvider.getFullPath(filename);
return new FileSystemResource(new File(path));
}
Very important is mime type in produces and also that, that name of the file is a part of the link so you has to use #PathVariable.
HTML code looks like that:
<a th:href="#{|/dbreport/files/${file_name}|}">Download</a>
Where ${file_name} is generated by Thymeleaf in controller and is i.e.: result_20200225.csv, so that whole url behing link is: example.com/aplication/dbreport/files/result_20200225.csv.
After clicking on link browser asks me what to do with file - save or open.
I had to add this to download any file
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition",
"attachment;filename="+"file.txt");
all code:
#Controller
public class FileController {
#RequestMapping(value = "/file", method =RequestMethod.GET)
#ResponseBody
public FileSystemResource getFile(HttpServletResponse response) {
final File file = new File("file.txt");
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition",
"attachment;filename="+"file.txt");
return new FileSystemResource(file);
}
}
This can be a useful answer.
Is it ok to export data as pdf format in frontend?
Extending to this, adding content-disposition as an attachment(default) will download the file. If you want to view it, you need to set it to inline.
i want to make a rest api controller (spring boot) that when petitioned with a get will allow me to download an excel file. currently i have this endpoint:
#RequestMapping(value = "/download.xls", method = RequestMethod.GET)
public ResponseEntity Survey_Reports(#RequestParam(value = "evaluated") String evaluated){
return surveyService.getSurveysFile(evaluated);
}
wich ultimately calls to this method:
public static ResponseEntity getDownloadResponse() {
File file2Upload = new File("Survey_Reports.xls");
Path path = Paths.get(file2Upload.getAbsolutePath());
ByteArrayResource resource = null;
try {
resource = new ByteArrayResource(Files.readAllBytes(path));
} catch (IOException e) {
logger.error("there was an error getting the file bytes ", e);
}
return ResponseEntity.ok()
.contentLength(file2Upload.length())
//this line doesnt seem to work as i set the file format in the controller request mapping
.contentType(MediaType.parseMediaType("application/vnd.ms-excel"))
.body(resource);
}
everything seems to work semi-fine as i get the download.xls(as the mapping) file correclty, but now i want to make the downloaded file have some specific name like: evaluatedName.xls or userDateEndDate.xls or some other stuff, is there a way to edit the response entity to do so? so that i dont have to name the mapping "download.xls"
In context of HttpServletResponse response you can do this like this
response.setContentType("application/csv");
response.setHeader("Content-Disposition", "attachment; filename=" + csvName);
and for ResponseEntity i assume you can use something like this:
ResponseEntity.ok().header("Content-Disposition","attachment; filename=" + csvName );
I have an application that works on Java and AngularJS.
I create pdf files with Java, using FileOutputStream to store them:
#RequestMapping(value = "/getPdf",
method = RequestMethod.GET)
#RolesAllowed(AuthoritiesConstants.USER)
public List<String> getPdf(#RequestParam(value = "id") Long id){
FileOutputStream fileStream = null;
String fileName = textRepository.findOne(id).getTitle() + ".pdf";
String text = textRepository.findOne(id).getUserText();
try {
fileStream = new FileOutputStream(fileName);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
// create an API client instance
Client client = new Client("", "");
client.convertHtml(text, fileName);
try {
fileStream.close();
} catch (IOException e) {
e.printStackTrace();
}
List<String> out = new ArrayList<>();
out.add(fileName);
return out;
}
They are created in the root directory of my application.
Now I want to implement a functionality that lets the user to download a pdf by clicking on a link or a button. I have tried with $window.open(), but I can't manage to get the path to my pdf file.
$scope.getPdf = function (id) {
TextService.getPdf(id).success(function(data){
$window.open('../../../../../../' + data[0], '_blank', 'download');
});
};
Here i get an error saying that Cannot GET /data.pdf
EDIT - solved the problem
I had to do a POST method that sends the file:
#RequestMapping(value = "/getPdf",
method = RequestMethod.POST)
#RolesAllowed(AuthoritiesConstants.USER)
public ResponseEntity<byte[]> getPdf(#RequestBody Long id){
String filename = textRepository.findOne(id).getTitle() + ".pdf";
String text = textRepository.findOne(id).getUserText();
ByteArrayOutputStream pdf = new ByteArrayOutputStream();
// create an API client instance
Client client = new Client("", "");
client.convertHtml(text, pdf);
byte[] content = pdf.toByteArray();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType("application/pdf"));
headers.setContentDispositionFormData("inline", filename);
headers.setCacheControl("must-revalidate, post-check=0, pre-check=0");
ResponseEntity<byte[]> response = new ResponseEntity<>(content, headers, HttpStatus.OK);
return response;
}
and back to my AngularJS client i have a service that calls the Java method:
angular.module("eddieApp")
.factory("TextService", function($http){
return{
getPdf: function(id){
return $http.post('texts/getPdf', id, { responseType: 'arraybuffer' });
}
};
});
Now in the controller all i had to do is call the service and open a window with the pdf:
$scope.getPdf = function (id) {
TextService.getPdf(id).success(function(data){
var file = new Blob([data], {type: 'application/pdf'});
var fileURL = ($window.URL || $window.webkitURL).createObjectURL(file);
$window.open(fileURL, '_blank', 'download');
});
};
Hope it helps someone!
If you are serving the angular portion from a webserver, you cannot access the filesystem of the server. That would be a severe security problem.
Why not provide another #RequestMapping(value = "/getFile") which serves the file directly to the user, using the proper MIME type as well?
Here is a similar question Return generated pdf using spring MVC with an answer on how to do that.
i hope to help. I think the problem is in the window.open, first should exist a service that makes a post with the url, call the service will pass the id and now if you make the window.open
Hi have written controller class like below. I am trying to get file from mongo db and try to download it.
organizationFileAttachmentService.setUser(getUser());
GridFSDBFile file = organizationFileAttachmentService.getGridFSDBFileById(new ObjectId(id), "File");
if (file != null) {
byte[] content = organizationFileAttachmentService.findByIdAndBucket(new ObjectId(id), "File");
try {
int size = content.length;
InputStream is = null;
byte[] b = new byte[size];
try {
is = new ByteArrayInputStream(content);
is.read(b);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (is != null)
is.close();
} catch (Exception ex) {
}
}
response.setContentType(file.getContentType());
// String attachment =
// "attachment; filename=\""+file.getFilename()+"\"";
String attachment = "attachment; filename=" + file.getFilename();
// response.setContentLength(new
// Long(file.getLength()).intValue());
response.setCharacterEncoding(file.getMD5());
response.setHeader("content-Disposition", attachment);// "attachment;filename=test.xls"
// copy it to response's OutputStream
// FileCopyUtils.copy(is, response.getOutputStream());
IOUtils.copy(is, response.getOutputStream());
response.flushBuffer();
is.close();
} catch (IOException ex) {
_logger.info("Error writing file to output stream. Filename was '" + id + "'");
throw new RuntimeException("IOError writing file to output stream");
}
but i am not able to down load file. can any one help me.
In case you missed it, Spring provides various built in resource handlers.
http://docs.spring.io/spring/docs/3.2.5.RELEASE/spring-framework-reference/html/resources.html#resources-implementations
If your method returns one of those (perhaps the ByteArrayResource in your case), then you just need a couple of annotations on the interface like so:
#RequestMapping(value = "/foo/bar/{fileId}",
method = RequestMethod.GET,
produces = { MediaType.APPLICATION_OCTET_STREAM_VALUE })
#ResponseBody FileSystemResource downloadFile(Long fileId);
No fiddling with encodings and headers for you that way. I'd recommend trying that before rolling your own.
Edit: The above worked fine in Spring 3.1.4. It no longer works for 3.2.x or 4.x. Whereas previously, the produces = { MediaType.APPLICATION_OCTET_STREAM_VALUE } would cause Spring to add the appropriate headers, it now treats that as a restriction. If accessing the URL with a standard web browser, an accept header of "application/octet-stream" will not be sent. Spring will therefore return a 406 error. To get it working again, such a method needs to be re-written without the "produces" attribute. Instead, add HttpServletResponse to the method arguments and add the header inside the method. i.e.:
#RequestMapping(value = "/foo/bar/{fileId}",
method = RequestMethod.GET)
#ResponseBody FileSystemResource downloadFile(
Long fileId, HttpServletResponse response) {
...
response.setHeader( "Content-Disposition", "attachment;filename=" + fileName );
...
}
Edit redux:
Now using Spring 4.0.7 via Spring Boot 1.1.8. It would appear that setting the produces = { MediaType.APPLICATION_OCTET_STREAM_VALUE } instruction is now working again. Just having that instruction seems to be enough for all the browsers I have tried. Note however, that I have also found that it does not set the Content-Disposition, which is left as application/json. Although this doesn't seem to be an issue for browsers, I have come across bugs in PHP client applications, which seem to behave only based on the Content-Disposition. So it seems that the current solution is to do both of the above!
I have changed my request as GET and added request in anchor tag in html. Aslo changed my code as
#RequestMapping(value = "/getFileById/{id}", method = RequestMethod.GET)
public #ResponseBody
void download(#PathVariable String id, HttpServletRequest request, HttpServletResponse response) throws IOException {
organizationFileAttachmentService.setUser(getUser());
GridFSDBFile file = organizationFileAttachmentService.getGridFSDBFileById(new ObjectId(id), "File");
if (file != null) {
try {
response.setContentType(file.getContentType());
response.setContentLength((new Long(file.getLength()).intValue()));
response.setHeader("content-Disposition", "attachment; filename=" + file.getFilename());// "attachment;filename=test.xls"
// copy it to response's OutputStream
IOUtils.copyLarge(file.getInputStream(), response.getOutputStream());
} catch (IOException ex) {
_logger.info("Error writing file to output stream. Filename was '" + id + "'");
throw new RuntimeException("IOError writing file to output stream");
}
}
}
Now it is working fine for me.
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 a PDF generation framework like iText. Any better way?
However, my main problem is how do I allow the user to download a file through a Spring Controller?
#RequestMapping(value = "/files/{file_name}", method = RequestMethod.GET)
public void getFile(
#PathVariable("file_name") String fileName,
HttpServletResponse response) {
try {
// get your file as InputStream
InputStream is = ...;
// copy it to response's OutputStream
org.apache.commons.io.IOUtils.copy(is, response.getOutputStream());
response.flushBuffer();
} catch (IOException ex) {
log.info("Error writing file to output stream. Filename was '{}'", fileName, ex);
throw new RuntimeException("IOError writing file to output stream");
}
}
Generally speaking, when you have response.getOutputStream(), you can write anything there. You can pass this output stream as a place to put generated PDF to your generator. Also, if you know what file type you are sending, you can set
response.setContentType("application/pdf");
I was able to stream line this by using the built in support in Spring with it's ResourceHttpMessageConverter. This will set the content-length and content-type if it can determine the mime-type
#RequestMapping(value = "/files/{file_name}", method = RequestMethod.GET)
#ResponseBody
public FileSystemResource getFile(#PathVariable("file_name") String fileName) {
return new FileSystemResource(myService.getFileFor(fileName));
}
You should be able to write the file on the response directly. Something like
response.setContentType("application/pdf");
response.setHeader("Content-Disposition", "attachment; filename=\"somefile.pdf\"");
and then write the file as a binary stream on response.getOutputStream(). Remember to do response.flush() at the end and that should do it.
With Spring 3.0 you can use the HttpEntity return object. If you use this, then your controller does not need a HttpServletResponse object, and therefore it is easier to test.
Except this, this answer is relative equals to the one of Infeligo.
If the return value of your pdf framework is an byte array (read the second part of my answer for other return values) :
#RequestMapping(value = "/files/{fileName}", method = RequestMethod.GET)
public HttpEntity<byte[]> createPdf(
#PathVariable("fileName") String fileName) throws IOException {
byte[] documentBody = this.pdfFramework.createPdf(filename);
HttpHeaders header = new HttpHeaders();
header.setContentType(MediaType.APPLICATION_PDF);
header.set(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=" + fileName.replace(" ", "_"));
header.setContentLength(documentBody.length);
return new HttpEntity<byte[]>(documentBody, header);
}
If the return type of your PDF Framework (documentBbody) is not already a byte array (and also no ByteArrayInputStream) then it would been wise NOT to make it a byte array first. Instead it is better to use:
InputStreamResource,
PathResource (since Spring 4.0) or
FileSystemResource,
example with FileSystemResource:
#RequestMapping(value = "/files/{fileName}", method = RequestMethod.GET)
public HttpEntity<byte[]> createPdf(
#PathVariable("fileName") String fileName) throws IOException {
File document = this.pdfFramework.createPdf(filename);
HttpHeaders header = new HttpHeaders();
header.setContentType(MediaType.APPLICATION_PDF);
header.set(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=" + fileName.replace(" ", "_"));
header.setContentLength(document.length());
return new HttpEntity<byte[]>(new FileSystemResource(document),
header);
}
If you:
Don't want to load the whole file into a byte[] before sending to the response;
Want/need to send/download it via InputStream;
Want to have full control of the Mime Type and file name sent;
Have other #ControllerAdvice picking up exceptions for you (or not).
The code below is what you need:
#RequestMapping(value = "/stuff/{stuffId}", method = RequestMethod.GET)
public ResponseEntity<FileSystemResource> downloadStuff(#PathVariable int stuffId)
throws IOException {
String fullPath = stuffService.figureOutFileNameFor(stuffId);
File file = new File(fullPath);
long fileLength = file.length(); // this is ok, but see note below
HttpHeaders respHeaders = new HttpHeaders();
respHeaders.setContentType("application/pdf");
respHeaders.setContentLength(fileLength);
respHeaders.setContentDispositionFormData("attachment", "fileNameIwant.pdf");
return new ResponseEntity<FileSystemResource>(
new FileSystemResource(file), respHeaders, HttpStatus.OK
);
}
More on setContentLength(): First of all, the content-length header is optional per the HTTP 1.1 RFC. Still, if you can provide a value, it is better. To obtain such value, know that File#length() should be good enough in the general case, so it is a safe default choice.
In very specific scenarios, though, it can be slow, in which case you should have it stored previously (e.g. in the DB), not calculated on the fly. Slow scenarios include: if the file is very large, specially if it is on a remote system or something more elaborated like that - a database, maybe.
InputStreamResource
If your resource is not a file, e.g. you pick the data up from the DB, you should use InputStreamResource. Example:
InputStreamResource isr = new InputStreamResource(...);
return new ResponseEntity<InputStreamResource>(isr, respHeaders, HttpStatus.OK);
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:
ByteArrayResource - based in byte[]
FileSystemResource - for a File or a Path
UrlResource - retrieved from java.net.URL
GridFsResource - a blob stored in MongoDB
ClassPathResource - for files in classpath, for example files from resources directory. My answer to question "Read file from resources folder in Spring Boot" explains how to locate the resource in classpath in details
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
This code is working fine to download a file automatically from spring controller on clicking a link on jsp.
#RequestMapping(value="/downloadLogFile")
public void getLogFile(HttpSession session,HttpServletResponse response) throws Exception {
try {
String filePathToBeServed = //complete file name with path;
File fileToDownload = new File(filePathToBeServed);
InputStream inputStream = new FileInputStream(fileToDownload);
response.setContentType("application/force-download");
response.setHeader("Content-Disposition", "attachment; filename="+fileName+".txt");
IOUtils.copy(inputStream, response.getOutputStream());
response.flushBuffer();
inputStream.close();
} catch (Exception e){
LOGGER.debug("Request could not be completed at this moment. Please try again.");
e.printStackTrace();
}
}
Below code worked for me to generate and download a text file.
#RequestMapping(value = "/download", method = RequestMethod.GET)
public ResponseEntity<byte[]> getDownloadData() throws Exception {
String regData = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.";
byte[] output = regData.getBytes();
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.set("charset", "utf-8");
responseHeaders.setContentType(MediaType.valueOf("text/html"));
responseHeaders.setContentLength(output.length);
responseHeaders.set("Content-disposition", "attachment; filename=filename.txt");
return new ResponseEntity<byte[]>(output, responseHeaders, HttpStatus.OK);
}
What I can quickly think of is, generate the pdf and store it in webapp/downloads/< RANDOM-FILENAME>.pdf from the code and send a forward to this file using HttpServletRequest
request.getRequestDispatcher("/downloads/<RANDOM-FILENAME>.pdf").forward(request, response);
or if you can configure your view resolver something like,
<bean id="pdfViewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView" />
<property name="order" value=”2″/>
<property name="prefix" value="/downloads/" />
<property name="suffix" value=".pdf" />
</bean>
then just return
return "RANDOM-FILENAME";
The following solution work for me
#RequestMapping(value="/download")
public void getLogFile(HttpSession session,HttpServletResponse response) throws Exception {
try {
String fileName="archivo demo.pdf";
String filePathToBeServed = "C:\\software\\Tomcat 7.0\\tmpFiles\\";
File fileToDownload = new File(filePathToBeServed+fileName);
InputStream inputStream = new FileInputStream(fileToDownload);
response.setContentType("application/force-download");
response.setHeader("Content-Disposition", "attachment; filename="+fileName);
IOUtils.copy(inputStream, response.getOutputStream());
response.flushBuffer();
inputStream.close();
} catch (Exception exception){
System.out.println(exception.getMessage());
}
}
something like below
#RequestMapping(value = "/download", method = RequestMethod.GET)
public void getFile(HttpServletResponse response) {
try {
DefaultResourceLoader loader = new DefaultResourceLoader();
InputStream is = loader.getResource("classpath:META-INF/resources/Accepted.pdf").getInputStream();
IOUtils.copy(is, response.getOutputStream());
response.setHeader("Content-Disposition", "attachment; filename=Accepted.pdf");
response.flushBuffer();
} catch (IOException ex) {
throw new RuntimeException("IOError writing file to output stream");
}
}
You can display PDF or download it examples here
If it helps anyone. You can do what the accepted answer by Infeligo has suggested but just put this extra bit in the code for a forced download.
response.setContentType("application/force-download");
In my case I'm generating some file on demand, so also url has to be generated.
For me works something like that:
#RequestMapping(value = "/files/{filename:.+}", method = RequestMethod.GET, produces = "text/csv")
#ResponseBody
public FileSystemResource getFile(#PathVariable String filename) {
String path = dataProvider.getFullPath(filename);
return new FileSystemResource(new File(path));
}
Very important is mime type in produces and also that, that name of the file is a part of the link so you has to use #PathVariable.
HTML code looks like that:
<a th:href="#{|/dbreport/files/${file_name}|}">Download</a>
Where ${file_name} is generated by Thymeleaf in controller and is i.e.: result_20200225.csv, so that whole url behing link is: example.com/aplication/dbreport/files/result_20200225.csv.
After clicking on link browser asks me what to do with file - save or open.
I had to add this to download any file
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition",
"attachment;filename="+"file.txt");
all code:
#Controller
public class FileController {
#RequestMapping(value = "/file", method =RequestMethod.GET)
#ResponseBody
public FileSystemResource getFile(HttpServletResponse response) {
final File file = new File("file.txt");
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition",
"attachment;filename="+"file.txt");
return new FileSystemResource(file);
}
}
This can be a useful answer.
Is it ok to export data as pdf format in frontend?
Extending to this, adding content-disposition as an attachment(default) will download the file. If you want to view it, you need to set it to inline.