File download from a REST API with Spring Boot

When calling a REST endpoint the response is generally returned as JSON. What if we want to give the user the chance to download the same data as a file?

We can build an endpoint that returns data as a downloadable file. The following example shows how easy it is to create an endpoint to download a file with Spring.

The project

We’ll build a Spring Boot project with spring-boot-starter-web as the only required dependency, lombok will allow us to reduce boilerplate code.

The model

Our endpoint will return the list of a company’s employees. The Employee class has 4 fields: id, firstName, lastName, dateOfBirth.

The EmployeeService interface has a single method that returns the list of all employees. To keep things simple, the implementation will return a hard-coded List of 4 employees. We won’t connect to a database to retrieve the data.

The controller

The most interesting part is the controller layer. We have an EmployeeController class (see below) that defines an endpoint GET /employees which takes an optional parameter called download. This parameter allows the user to choose which type of file to download from the endpoint. If the parameter is missing the endpoint will return JSON data in the response body, just like any traditional REST endpoint. Notice that the download parameter is not of type String, it is a custom enum called DownloadFormat. The enum has just one value so far, i.e. csv. Mapping the url request parameter to an enum will spare us the extra work of manually validating that parameter. A client calling the url with an invalid value like /employees?download=pdf will result in a 400 error response.

If the client requests the data as a csv file we need to rearrange our employees’ data to match the structure of a csv file. Each employee will become a string consisting of its concatenated field values’ separated by a semicolon, followed by a newline character. As a consequence, each employee will be on a separate line. The data will become one multiline string like this:

1;John;Doe;1980-07-14
2;Mary;Williams;1988-02-22
3;Edward;Roberts;1990-12-19
4;Elizabeth;McGee;1982-10-03

To make our ResponseEntity a downloadable file we need to tweak the response headers. We’ll set the Content-Type  to text/plain, but most important we’ll set the Content-Disposition to attachment; filename=employees.csv. The keyword here is “attachment”. This will instruct the browser to download the file in the response rather than render the content on the page. The body attribute of the ResponseEntity returned by our controller’s method will be the csv string content seen above, wrapped in a ByteArrayResource.

Notice how all the logic related to the response’s elements (body’s content and header values) is encapsulated in the DownloadFormat enum. When we want to add further download format options, we’ll just add more constants to the enum. Each constant defines two fields: first a MIME type, and second a function to convert the employees’ list to a byte array. No change to the EmployeeController class code will be necessary, in accordance with the open-closed principle of object oriented programming.

Testing

To test our endpoint we can launch the application using Spring Boot’s embedded Tomcat server. The fastest way is to use the command mvn spring-boot:run from the project’s root folder.

First we call the endpoint in our browser without the download parameter. As a consequence, the browser displays the employees on the page, serialized as JSON.

Then we call the endpoint in our browser with the ?download=csv parameter. The browser will automatically download our csv file. Depending on your browser’s settings, a popup window to choose the destination folder could open.

And if we open the file with MS Excel we’ll see our data in columns and rows as expected.

Conclusion

We’ve seen how easily we create a REST endpoint to download a file with Spring. One option is to create separate endpoints: one to returns the data in the response body, one to download the response. This example shows how it is even possible to combine the two approaches. We created a single endpoint that lets the client choose whether to get the data in the response body or download a file. The presence of a certain query parameter in the URL is what triggers the download.

The example project can be downloaded from GitLab.