Explain Codes LogoExplain Codes Logo

Download a file from Spring boot rest service

java
resource-management
file-downloads
spring-boot
Alex KataevbyAlex Kataev·Feb 6, 2025
TLDR

For those in a hurry, we can setup file downloads in Spring Boot using ResponseEntity<Resource>. Set the correct headers via HttpHeaders and use a FileSystemResource for the filepath. Define the content type as "application/octet-stream" in ResponseEntity for downloading binary data.

@GetMapping("/download") public ResponseEntity<Resource> downloadFile(@RequestParam String filename) { // Just like Boots and Swiper, we created a path. Path filePath = Paths.get("files", filename); Resource fileResource = new FileSystemResource(filePath); HttpHeaders headers = new HttpHeaders(); // Add this header, or the browser will be as confused as a chameleon in a bag of Skittles. headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filePath.getFileName().toString() + "\""); return ResponseEntity.ok() .headers(headers) .contentLength(filePath.toFile().length()) .contentType(MediaType.APPLICATION_OCTET_STREAM) .body(fileResource); }

You've now got a /download endpoint, just hit it and the requested file named filename sprints to the browser.

Resource handling: Choose the right tool for the job

When dealing with file downloads, the correct resource selection is key. Both ByteArrayResource and InputStreamResource offer good performance, but alignment to the type of problem and file can optimize resource usage. That's like choosing the best utensil - Soup with fork? I don't think so!

For smaller files, ByteArrayResource has an edge with their better memory handling characteristics. On other hand, for streaming files without loading complete file in memory, InputStreamResource is often the choice.

Resource TypePreferred Usage
ByteArrayResourceSmall Files
InputStreamResourceStreaming Files

When dealing with IOExceptions, remember to always handle the try catch appropriately - Trapping exceptions is not just for Pokémon.

To stream large files without consuming excessive memory, StreamingResponseBody is your memory-friendly friend. Don’t forget to use a TaskExecutor for asynchronous handling of download request and to buffer your OutputStreams. Just like buffering a YouTube video, but for files.

Advanced use cases, tips & tricks

Direct file access with InputStream

To directly access a file's data stream:

@GetMapping("/stream") public ResponseEntity<InputStreamResource> streamFile(@RequestParam String filename) throws IOException { Path path = Paths.get("files", filename); // Jump into the file's data stream. No swimming skills required. InputStream inputStream = new FileInputStream(path.toFile()); return ResponseEntity.ok() .contentType(MediaType.parseMediaType("application/octet-stream")) .body(new InputStreamResource(inputStream)); }

Frontend downloads

While dealing with frontend downloads, remember to set the responseType to 'blob' on Axios calls. Libraries like js-file-download can also help to manage downloads well.

Serving large files

For streaming large files, use the StreamingResponseBody:

@GetMapping("/stream-large-file") public ResponseEntity<StreamingResponseBody> streamLargeFile(@RequestParam String filename) { StreamingResponseBody stream = outputStream -> { Path path = Paths.get("files", filename); // Buffer your stream like you are gearing up for a Netflix marathon. try (BufferedInputStream in = new BufferedInputStream(new FileInputStream(path.toFile()))) { byte[] buffer = new byte[1024]; int len; while ((len = in.read(buffer)) > 0) { outputStream.write(buffer, 0, len); } } }; HttpHeaders headers = new HttpHeaders(); headers.add("Content-Disposition", "attachment; filename=" + filename); return ResponseEntity.ok() .headers(headers) .body(stream); }

Handling potential landmines

Keep an eye on potential issues and handle them gracefully:

  • File not found: Always validate existence of file beforehand.
  • Client disconnections: Sudden client disconnections need management.
  • Security: Put checks and controls in place for authorized access only.