Downloading a file from spring controllers
Solution 1
@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");
Solution 2
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));
}
Solution 3
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.
Solution 4
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);
}
Solution 5
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);
Comments
-
MilindaD over 2 years
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?
-
GaryF about 13 yearsThis is pretty much what I was about to say, but you should probably also set the response type header to something appropriate for the file.
-
Infeligo about 13 yearsYep, just edited the post. I had various file types generated, so I left it to the browser to determine the content type of the file by its extension.
-
Jan Vladimir Mostert almost 12 yearsForgot the flushBuffer, thanks to your post, I saw why mine wasn't working :-)
-
Matt almost 12 yearsRemember to close InputStream.
-
Powerlord over 11 yearsAny particular reason to use Apache's
IOUtils
instead of Spring'sFileCopyUtils
? -
Ajay over 11 yearsI think this is the best & most clean way of handling file requests.
-
lrkwz over 11 yearsabsolutely the best response see also stackoverflow.com/questions/3526523/… for pathvariable being truncated (spring 3.0.7 in my case)
-
Jason over 11 yearsNormally yes you close your streams. In this case, though, I think not. stackoverflow.com/questions/1159168/…
-
Ruairi O'Brien almost 11 yearsThis method looks great but can't get it to work 100%. I don't want to open a question for this but on the off chance someone can help. I can't seem to set any details on what is returned such as the file name. Also on good old IE7 & IE8 the file extension seems to get lost somehow so it doesn't even know what file type is being downloaded. Does anyone have some useful example explaining the usage of this to a Spring noob. I'm currently just using HttpServletResponse but would like to use the above method if I could get to send back a little more details in the response.
-
chzbrgla almost 11 yearsThis works. But the file (.csv file) is displayed in the browser and not downloaded - how can I force the browser to download?
-
Marsellus Wallace over 10 yearsCan I set the file name? I'm actually interested in the file extension like .pdf or .csv
-
David Kago over 10 yearsYou can add produces = MediaType.APPLICATION_OCTET_STREAM_VALUE to the @RequestMapping to force download
-
Enginer over 10 yearsAlso You should add <bean class="org.springframework.http.converter.ResourceHttpMessageConverter"/> to messageConverters list (<mvc:annotation-driven><mvc:message-converters>)
-
Kacper86 over 10 yearsAnd can you set content-type with this method?
-
рüффп over 10 years@Gevorg look lobster1234 answer, you can set the header of the response and put any filename you want (the client will get).
-
Rishav Dry about 10 years-1 this will un-neccessarily load the whole file in memory can easily casue OutOfMemoryErrors.
-
Ralph about 10 years@FaisalFeroz: yes this is right, but the file document is anyway created in memory (see the question: "PDF needs to be generated within the code"). Anyway - what is your solution that overcome this problem?
-
Black about 10 yearsisn't the 'Spring' way to set the content type like this?
@RequestMapping(value = "/foo/bar", produces = "application/pdf")
-
JayL about 10 yearsClassPathResource is a nice alternative to FileSystemResource.
-
azerafati almost 10 yearsIf I need two view resolvers, how can I also return the name of resolver or choose it in controller??
-
Amr Mostafa over 9 yearsYou may also use ResponseEntity which is a super of HttpEntity which allows you to specify the response http status code. Example:
return new ResponseEntity<byte[]>(documentBody, headers, HttpStatus.CREATED)
-
Ralph over 9 years@Amr Mostafa:
ResponseEntity
is a subclass ofHttpEntity
(but I get it) on the other hand 201 CREATED is not what I would use when I return just an view to the data. (see w3.org/Protocols/rfc2616/rfc2616-sec10.html for 201 CREATED) -
Stephane over 9 yearsYou don't advise for the use of the FileSystemResource class ?
-
Ralph about 9 yearsIs there a way to set the
Content-Disposition
header with this way? -
Scott Carlson about 9 yearsI didn't have a need for that, but I think you could add HttpResponse as a parameter to the method, and then "response.setHeader("Content-Disposition", "attachment; filename=somefile.pdf");"
-
acdcjunior about 9 yearsActually, I do believe it is OK to use the
FileSystemResource
there. It is even advisable if your resource is a file. In this sample,FileSystemResource
can be used whereInputStreamResource
is. -
acdcjunior about 9 yearsAbout the file length calculation part: If you are worried, don't be.
File#length()
should be good enough in the general case. I just mentioned it because it does can be slow, specially if the file is in a remote system or something more elaborated like that - a database, maybe?. But only worry if it becomes a problem (or if you have hard evidence it will become one), not before. The main point is: you are making an effort to stream the file, if you have to preload all of it before, then the streaming ends up making no difference, eh? -
Dmytro Plekhotkin almost 9 yearsHere is a better solution: stackoverflow.com/questions/16652760/…
-
coding_idiot over 8 yearswhy does the above code not working for me ? It downloads 0 bytes file. I checked and made sure ByteArray & ResourceMessage converters are there. Am I missing something ?
-
acdcjunior over 8 yearsWhy are you worrying about ByteArray & ResourceMessage converters?
-
Rose over 8 years@Francis what if your application downloads different file types? Lobster1234's answer enables you to dynamically set the content disposition.
-
Black over 8 yearsthat's true @Rose, but I believe it would be better practice to define different end-points per format
-
Rose over 8 yearsI guess not, because it's not scalable. We are currently supporting a dozen types of resources. We might support more file types based on what users want to upload in that case we might end up with so many end points essentially doing the same thing. IMHO there has to be only one download end point and it handles multitude of file types. @Francis
-
Black over 8 yearsit's absolutely "scalable", but we can agree to disagree whether it's the best practice
-
sendreams about 8 years@Infeligo, i use these code, but my chrome not show a download link for the file. how can i do then?
-
Sajad almost 8 years@ScottCarlson Can i create a custom class and extend from
FileSystemResource
and use that class type as return type of download controller? -
Ashley about 7 yearsSame here. Blank PDFs.
-
Sam YC almost 7 yearsWhat would be the stream buffer size of
InputStreamResource
? -
Alex Vazquez Fente almost 7 yearsI like this way more than @Infeligo's one but if the file doesn't exist, this doesn't throw any exception and the client download hangs forever (until client times out). Is there something wrong in the builtin ResourceHttpMessageConverter?
-
Alex Vazquez Fente almost 7 yearsTo answer myself: it seems ResourceHttpMessageConverter silently discards Exceptions for some reasons related to SPR-12999 and SPR-13620. To get what I want (an error if the file does not exist or some other error happens trying to stream the file) I had to write a custom converter extending ResourceHttpMessageConverter (overwriting writeContent) and a custom web config extending WebMvcConfigurerAdapter (overwriting configureMessageConverters).
-
Tom over 6 yearsBe careful with
response.flushBuffer()
! If your Controller is@Transactional
, it will send a response even if the transaction should be rolled back, which is most likely not what you want. -
Comencau about 6 years@Powerlord Spring method closes the streams, Apache one does not. There are debates if the Servlet response output stream should be closed in the Controller code or by the Servlet container ...
-
Alexandru Severin over 5 yearsIs there a reason why you are replacing whitespaces with underscore in the filename? You can wrap it in quotes to send the actual name.
-
Ralph over 5 years@Alexandru Severin: you are right, I would use quotes nowadays. But when I remember right, there was some problems with Whitespaces (not in the http header) but with the browser or fielsystem (maybe just with some strange file system in our testing environment)
-
Hussain over 5 yearsSpecifying
respHeaders.setContentLength(12345678);
makes the download super slow I guess. -
bharal over 4 yearswhat is myService?
-
Mathias Mamsch over 3 yearsThis should be the accepted answer. It seems to provide the only clean handling of ContentDisposition and gives clear explanations.
-
Dharmendrasinh Chudasama over 3 yearsyou will face an issue in case of filename contains space,;, etc
-
Ilya Serbis over 3 years
setContentLength()
is optional so it can be omitted -
Tobi Akinyemi over 3 yearsDefinitely the best answer
-
RamPrakash almost 3 yearswhat do you mean by - If your resource is not a file, then use
new InputStreamResource(new FileInputStream(file));
.. what is this file? -
acdcjunior over 2 years@RamPrakash actually, by "if it is not a file" I mean if you want to send to the user some resource from other source than a file (e.g. you are streaming directly from a database). In this case, you should construct an
InputStreamResource
object. (The example I had left, which I now removed, had an example of constructing anInputStreamResource
using a file -- which was just an example but you shouldn't do anyway, since if you do want a file you should construct aFileSystemResource
directly instead). -
Indrajit Kanjilal over 2 yearsWorks perfectly, and works with "springdoc-openapi-ui" v1.5.11, swagger-ui. The "Download" link appears as expected with "attachment()" flag.