- Return file from REST webservice
- Detailed Video Notes
- Project set up
- Creating a Request Method
- Prevent caching
- ByteArrayHttpMessageConverter
- Send file with REST API
- Dependencies
- Return file content via REST API
- Return file via REST API so it is downloaded
- Return file with filename
- Stream file using InputStreamResource object
Return file from REST webservice
It is easy serving a static asset from a spring web application but what if you needed to fetch a file from a database or a file system? In this episode find out how to return a file from a spring controller.
Detailed Video Notes
In this example we will show how to read a PDF from the classpath simulating a read from a database or a file system and downloading it from a RESTful web service. While we use a PDF you could substitute just about any file format such as gif, jpeg, tiff, png, zip, jars, wars, ear or any other binary format that you need to stream to your browser. More examples show how to make a get request for json and return xml from a spring rest service
Project set up
Visiting spring initializer website and selecting web project dependency we will create a spring boot skeleton project. Opening the project in eclipse we will copy a sample pdf named pdf-sample.pdf into the resource directory. Finally creating a @RestController named DownloadPDFController we will stub in the method in which we will want to map a request to.
@RestController public class DownloadPDFController >
Creating a Request Method
In the DownloadPDFController we will create a default request mapping for the application. The return type will be a ResponseEntity , an extension of HttpEntity that adds a HttpStatus code, and will be of type InputStreamResource . Next for demonstration purposes we fetch a file named pdf-sample.pdf from the classpath by using ClassPathResource . This could be substituted by reading a file from a network drive or retrieving it from a database. Once we have hold of the file we can use ResponseEntityBuilder introduced in Spring 4.1 setting the length, content type and the body of the response to be the file.
Let’s go ahead and run our example and navigate to http://localhost:8080/ to view the pdf.
@RequestMapping(value = "/", method = RequestMethod.GET, produces = "application/pdf") public ResponseEntityInputStreamResource> downloadPDFFile() throws IOException ClassPathResource pdfFile = new ClassPathResource("pdf-sample.pdf"); return ResponseEntity .ok() .contentLength(pdfFile.contentLength()) .contentType( MediaType.parseMediaType("application/octet-stream")) .body(new InputStreamResource(pdfFile.getInputStream())); >
Prevent caching
Often times when you are returning document you may want to prevent caching so that the next time a request is made it returns the most current version. Luckily the ResoponseEnity allows you to set the headers on the response. We will create HttpHeaders object setting Cache-Control, Pragma and Expires properties. In the event where you want to prevent caching across your entire application you could set the response in either a Filter or a HandlerInterceptor .
@RequestMapping(value = "/", method = RequestMethod.GET, produces = "application/pdf") public ResponseEntityInputStreamResource> downloadPDFFile() throws IOException ClassPathResource pdfFile = new ClassPathResource("pdf-sample.pdf"); HttpHeaders headers = new HttpHeaders(); headers.add("Cache-Control", "no-cache, no-store, must-revalidate"); headers.add("Pragma", "no-cache"); headers.add("Expires", "0"); return ResponseEntity .ok() .headers(headers) .contentLength(pdfFile.contentLength()) .contentType(MediaType.parseMediaType("application/octet-stream")) .body(new InputStreamResource(pdfFile.getInputStream())); >
ByteArrayHttpMessageConverter
Just in case you need it, you may need to register a custom message converter named ByteArrayHttpMessageConverter . To do this, if you are using spring boot you can create a bean named «customConverters» creating ByteArrayHttpMessageConverter and populating a HttpMessageConverters object.
@SpringBootApplication public class ReturnFileFromSpringRestWebserviceApplication public static void main(String[] args) SpringApplication.run( ReturnFileFromSpringRestWebserviceApplication.class, args); > @Bean public HttpMessageConverters customConverters() ByteArrayHttpMessageConverter arrayHttpMessageConverter = new ByteArrayHttpMessageConverter(); return new HttpMessageConverters(arrayHttpMessageConverter); > >
Thanks for joining in today’s level up, have a great day!
Return file from REST webservice posted by Justin Musgrove on 01 June 2015
Tagged: java, java-tutorial, spring, and rest
All the code on this page is available on github: View the source
Send file with REST API
Besides working on JSON objects, Spring Boot application can also make files available for downloading via REST API. We can build an application that exposes endpoints that can be used to download raw files, images, CSV files etc. We are not only limited to well-defined objects serialized to a JSON format. See how to do that.
Dependencies
I use a simple Spring Boot project to quickly set up a project. I can be even more lazy than that. Ups, I meant to be more time efficient. The short path I have in my mind is a use of Spring Boot Starters. I need only one — spring-boot-starter-web.
dependency>
groupId>org.springframework.bootgroupId>
artifactId>spring-boot-starter-webartifactId>
dependency>
It triggers automatically adding everything that is necessary for building a Spring application: like spring-core, spring-beans. Moreover, it is dependent on web libraries that allow building a REST API like spring-webmvc, embeded Tomcat and using the most popular REST serialization/deserialization format — JSON, by adding jackson libraries.
Return file content via REST API
Normally, if I wanted to have an HTTP GET endpoint, I would use a Spring @RestController annotation on a class and a @GetMapping annotation on a method. The method would return an object that would be automatically serialized to JSON by Spring and Jackson. That approach is very common and works fine in various scenarios. Why not to try that for files?
@RestController
@RequestMapping("files")
public class FileController
@GetMapping("/csv")
public byte[] getCsvFile() throws URISyntaxException, IOException
String csvFilePath = "static/sample.csv";
URL url = getClass().getClassLoader().getResource(csvFilePath);
if (url == null)
throw new IOException("File " + csvFilePath + " not found");
>
Path path = Paths.get(url.toURI());
String fileContent = Files.readString(path);
return fileContent.getBytes();
>
Let’s analyze what the above code does. FileController is a Spring Rest Controller with the getCsvFile method that is mapped to GET requests on files/scv URL. The method returns a byte array as it is a very generic type that actually describes any file content. Putting it in different words, the method returns a file content.
Now, it is time to take a look at how the method does its job. There is no magic. It must just read the content of the file as a byte array and return it. In this case I used the getResource method of a ClassLoader . That was possible, because the file is a part of the project. I have it in the resources/static/ directory.
Then, the readString method from java.nio.Files utility class is used to read the file content as a String. Finally, it is converted to a byte array and returned.
When you start the application and access the endpoint http://localhost:8080/files/csv via an internet browser, you will see the file content on the screen.
That effect is exactly aligned with what I implemented. The method read the file and returned its content. So the endpoint returned the content and the browser displayed it on the page.
Return file via REST API so it is downloaded
Sending just a file content may not be enough in many cases. Sometimes, we may want to build a REST endpoint that will explicitly return a file so the client (for example, an internet browser) will recognize it properly as a file and will download it. It is again easily possible with the following code.
@RequestMapping("/byteimage")
public ResponseEntityByteArrayResource> getByteImage() throws URISyntaxException, IOException
String imageFilePath = "static/image.png";
URL url = getClass().getClassLoader().getResource(imageFilePath);
if (url == null)
throw new IOException("File " + imageFilePath + " not found");
>
File file = new File(url.toURI());
Path path = Paths.get(url.toURI());
return ResponseEntity.ok()
.contentLength(file.length())
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(new ByteArrayResource(Files.readAllBytes(path)));
>
It is important to notice a few things that I did differently than in the previous example. This time, I used the @RequestMapping annotation instead of @GetMapping . @GetMapping automatically wraps the returned object with a ResponseEntity , which I do not want this time. I need more control over what is actually returned by the endpoint. I specifically set a content length and a content type. These headers clearly tell the browser that it is a file, so the browser knows that it is better to just download it rather than displaying the content on the page.
Once I enter the URL (send a GET request to the endpoint), the browser downloads the file.
Return file with filename
At this point, an obvious problem becomes visible. Why the image has a weird file name — byteimage and has no file extension that would indicate the format? byteimage is just the last part of the endpoint URL. The ResponseEntity that was returned by the getByteImage method, does not specify a file name, so the browser had to be creative in that area. It can be changed. A file name may be specified in a Content-Disposition header.
@RequestMapping("/imagefile")
public ResponseEntityByteArrayResource> getImageFile() throws URISyntaxException, IOException
String imageFilePath = "static/image.png";
URL url = getClass().getClassLoader().getResource(imageFilePath);
if (url == null)
throw new IOException("File " + imageFilePath + " not found");
>
File file = new File(url.toURI());
Path path = Paths.get(url.toURI());
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=customname.png");
return ResponseEntity.ok()
.headers(headers)
.contentLength(file.length())
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(new ByteArrayResource(Files.readAllBytes(path)));
>
The above method sets a Content-Disposition header with filename property set to customname.png. When you enter the http://localhost:8080/files/imagefile URL in the browser, it will download the image with the predefined file name.
Stream file using InputStreamResource object
A file may be returned from a REST endpoint not only as a byte array or a ByteArrayResource . It may also be a stream. See below how it can be done with InputStreamResource .
@RequestMapping("/imageresource")
public ResponseEntityResource> getResource() throws URISyntaxException, IOException
String imageFilePath = "static/image.png";
URL url = getClass().getClassLoader().getResource(imageFilePath);
if (url == null)
throw new IOException("File " + imageFilePath + " not found");
>
File file = new File(url.toURI());
Resource resource = new InputStreamResource(new FileInputStream(file));
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=customname.png");
return ResponseEntity.ok()
.headers(headers)
.contentLength(file.length())
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(resource);
>
The result is very similar to the previous one. A file is downloaded with a custom name. But this time, it is streamed. Which means it is not necessarily read as a whole to the memory on the server side. This technique may be especially useful for large files.
It also may be worth mentioning that returning any kind of a Resource object by a controller method, does not require closing it manually. Spring is aware of what we are doing and it will close it once the transfer is complete.