
How to Handle File Response in Spring Boot REST APIs
In this article we’ll dive into three different ways for returning files in a Spring Boot application. Every approach has different trade-offs in terms of memory usage, performance, and complexity. We’ll provide guidance on selecting the best option for your use case.
We’ll look into generating a csv
file that will be downloaded automatically when the endpoint is called (Content-Disposition: attachment
)
Option 1: byte[]
@GetMapping("/download")
public ResponseEntity<byte[]> download() {
String content = "csv here";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.TEXT_PLAIN);
headers.setContentDisposition(ContentDisposition.attachment().filename("filename.csv").build());
return ResponseEntity.ok()
.headers(headers)
.body(content.getBytes(StandardCharsets.UTF_8));
}
Pros:
- Straightforward implementation
Cons:
- High memory usage, as the entire file is loaded into memory
- Performance issues with large files
Option 2: InputStreamResource
@GetMapping("/download")
public ResponseEntity<InputStreamResource> download() {
String content = "csv here";
ByteArrayInputStream inputStream = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.TEXT_PLAIN);
headers.setContentDisposition(ContentDisposition.attachment().filename("filename.csv").build());
return ResponseEntity.ok()
.headers(headers)
.body(new InputStreamResource(inputStream));
}
Pros:
- Memory efficient, as it streams data rather than loading everything at once
- More scalable than byte[] for larger files
Cons:
-
More complex than returning a byte array, you need to handle resource management and ensure the
InputStream
is closed
Option 3: Using StreamingResponseBody
@GetMapping("/download")
public ResponseEntity<StreamingResponseBody> download() {
String content = "csv here";
StreamingResponseBody stream = outputStream -> {
try (PrintWriter writer = new PrintWriter(new OutputStreamWriter(outputStream, StandardCharsets.UTF_8))) {
writer.println(content);
writer.flush();
}
};
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.TEXT_PLAIN);
headers.setContentDisposition(ContentDisposition.attachment().filename("filename.csv").build());
return ResponseEntity.ok()
.headers(headers)
.body(stream);
}
It is highly suggested to configure a TaskExecutor
for handling async requests and and avoid thread exhaust
@EnableAsync
@EnableScheduling
@Configuration
public class AsyncConfig implements AsyncConfigurer {
@Bean(name = "taskExecutor")
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(core pool size here);
executor.setMaxPoolSize(max pool size here);
executor.setQueueCapacity(queue capacity here);
return executor;
}
}
Pros:
- Less memory usage, it streams the response directly to the client
- Best for handling very large files
Cons:
- More complex error handling, it’s more difficult to set a custom status code if an error occurs while streaming
- More complex implementation
Conclusion
We had a look at different approaches for sending a CSV file response with Spring Boot. Considering pros and cons
for each option, we recommend using StreamingReponseBody
for large files, InputStreamResource
for medium files and byte[]
for small files