send file via REST

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.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</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.project structure

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.

csv file content

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 ResponseEntity<ByteArrayResource> 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.

See how it works.

image file downloaded

Once I enter the URL (send a GET request to the endpoint), the browser downloads the file.

Do not miss valuable content. You will receive a monthly summary email. You can unsubscribe anytime.

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 ResponseEntity<ByteArrayResource> 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.

image with filename 

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 ResponseEntity<Resource> 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.

The whole project source code is on https://github.com/dba-presents/files-in-rest-api

If you like what I do, consider buying me a coffee :)

Buy me a coffeeBuy me a coffee