The error java.io.IOException: Stream Closed indicates that the InputStream passed to the InputStreamResource has already been closed before it is read by the Spring framework to create the response.
Use Case
The use case where you may need to encounter this error is file download from Spring Boot App. Writing down bytes loaded in memory – as a Base64 String may be an option – only if you know that the files will not be very big. For anything above one megabyte the proper way to handle it is with Stream – lazy – without loading it all to memory.
Here are common reasons this might happen:
1. The InputStream is closed before being used
- If you have explicitly closed the
InputStreamsomewhere in your code after wrapping it in theInputStreamResource, this will result in theStream Closedexception when the framework tries to read it.
Example issue:
InputStream inputStream = new FileInputStream(file);
inputStream.close(); // Closing the stream prematurely
return ResponseEntity.ok()
.body(new InputStreamResource(inputStream)); // Fails because the stream is closed
2. InputStream is auto-closed
- If the
InputStreamwas opened in a try-with-resources block, it will be automatically closed at the end of the block before theResponseEntitycan use it.
Example issue:
try (InputStream inputStream = new FileInputStream(file)) {
return ResponseEntity.ok()
.body(new InputStreamResource(inputStream)); // Stream is closed when try block ends
}
3. Reusing a closed InputStream
- If the
InputStreamis read somewhere else before being passed to theInputStreamResource, the stream pointer reaches the end of the stream, and the stream might also be closed by the code consuming it.
Example issue:
InputStream inputStream = new FileInputStream(file);
byte[] buffer = inputStream.readAllBytes(); // Consumes the stream
// Passing the same stream to InputStreamResource will fail
return ResponseEntity.ok()
.body(new InputStreamResource(inputStream)); // Stream already consumed or closed
Solutions
- Ensure the InputStream is open and valid
- Make sure the
InputStreamyou pass toInputStreamResourceis not closed or consumed beforehand.
Correct Code:
InputStream inputStream = new FileInputStream(file); return ResponseEntity.ok() .contentType(MediaType.APPLICATION_OCTET_STREAM) .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileName + "\"") .body(new InputStreamResource(inputStream)); - Make sure the
- Avoid try-with-resources for InputStream
- Do not use try-with-resources if you plan to pass the
InputStreamtoInputStreamResource, as it closes the stream automatically.
- Do not use try-with-resources if you plan to pass the
- Use a fresh InputStream for each request
- If you are reading from a file, create a new
FileInputStreamfor every request.
Example:
return ResponseEntity.ok() .contentType(MediaType.APPLICATION_OCTET_STREAM) .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + fileName + "\"") .body(new InputStreamResource(new FileInputStream(file))); - If you are reading from a file, create a new
- Buffered InputStream for reusable streams
- If the stream is large and you want better performance, consider wrapping it in a
BufferedInputStreamto prevent accidental closure or reading.
- If the stream is large and you want better performance, consider wrapping it in a
- Debugging Tip
- Add logging or debugging to check if the
InputStreamis being consumed or closed elsewhere in your code.
- Add logging or debugging to check if the
Let me know if you’d like help analyzing your specific code structure!
Will Spring close the stream?
Yes, Spring will close the InputStream when it is used in a ResponseEntity with InputStreamResource. This is because Spring considers it the owner of the stream and ensures proper resource cleanup after the response is written.
Check out my Spring Boot Web Apps: https://programtom.com/dev/product-category/technologies/spring-boot-framework/?orderby=date-desc
