Spring
Feign
InputStream
Microservices
Java

How to get InputStream via Spring-Feign?

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

When you use Spring Cloud OpenFeign to download a file or another large payload, returning a raw InputStream from the client interface is usually not the best approach. The safer pattern is to return Feign's low-level Response or a Spring-friendly wrapper such as Resource, then open and close the stream yourself.

Why Returning InputStream Directly Is Awkward

Feign works by decoding an HTTP response into the declared Java return type. For JSON objects, strings, and small DTOs, that is exactly what you want. For a stream, however, you often need lower-level control over the response body lifecycle.

If the method returns InputStream directly, you depend on the decoder and surrounding infrastructure to keep the body open long enough and in the way you expect. That is brittle. In practice, returning feign.Response gives you direct access to response.body().asInputStream(), which is the most explicit and reliable pattern.

Here is a straightforward OpenFeign client for file downloads:

java
1import feign.Response;
2import org.springframework.cloud.openfeign.FeignClient;
3import org.springframework.web.bind.annotation.GetMapping;
4import org.springframework.web.bind.annotation.PathVariable;
5
6@FeignClient(name = "file-service", url = "${file.service.url}")
7public interface FileClient {
8
9    @GetMapping(value = "/files/{id}")
10    Response download(@PathVariable("id") String id);
11}

The important decision is the return type. Response exposes headers, status, and the response body stream without forcing eager deserialization.

Reading the Stream in a Service

Once you have the raw response, read it in a service layer and close it with try-with-resources.

java
1import feign.Response;
2import org.springframework.stereotype.Service;
3
4import java.io.IOException;
5import java.io.InputStream;
6import java.nio.file.Files;
7import java.nio.file.Path;
8
9@Service
10public class FileDownloadService {
11
12    private final FileClient fileClient;
13
14    public FileDownloadService(FileClient fileClient) {
15        this.fileClient = fileClient;
16    }
17
18    public Path downloadToTempFile(String id) throws IOException {
19        Response response = fileClient.download(id);
20
21        if (response.status() >= 400) {
22            throw new IOException("Remote service returned HTTP " + response.status());
23        }
24
25        Path target = Files.createTempFile("download-", ".bin");
26
27        try (Response ignored = response;
28             InputStream inputStream = response.body().asInputStream()) {
29            Files.copy(inputStream, target, java.nio.file.StandardCopyOption.REPLACE_EXISTING);
30        }
31
32        return target;
33    }
34}

This approach keeps ownership clear. Your application decides when the stream is consumed and when resources are released.

Alternative: Return a Resource

If you do not need true streaming control and the payload sizes are moderate, returning a Spring Resource can be more idiomatic.

java
1import org.springframework.cloud.openfeign.FeignClient;
2import org.springframework.core.io.Resource;
3import org.springframework.web.bind.annotation.GetMapping;
4import org.springframework.web.bind.annotation.PathVariable;
5
6@FeignClient(name = "file-service", url = "${file.service.url}")
7public interface FileClient {
8
9    @GetMapping(value = "/files/{id}")
10    Resource downloadAsResource(@PathVariable("id") String id);
11}

This is convenient for controller passthrough code, but it is less explicit than handling the Feign Response directly. For very large objects or strict resource management, Response is usually the better tool.

Passing the Stream Through a Controller

A common pattern is downloading from one service and relaying the content to the caller.

java
1import org.springframework.http.HttpHeaders;
2import org.springframework.http.MediaType;
3import org.springframework.http.ResponseEntity;
4import org.springframework.web.bind.annotation.GetMapping;
5import org.springframework.web.bind.annotation.PathVariable;
6import org.springframework.web.bind.annotation.RestController;
7
8@RestController
9public class DownloadController {
10
11    private final FileDownloadService service;
12
13    public DownloadController(FileDownloadService service) {
14        this.service = service;
15    }
16
17    @GetMapping("/downloads/{id}")
18    public ResponseEntity<byte[]> download(@PathVariable String id) throws Exception {
19        Path file = service.downloadToTempFile(id);
20        byte[] content = Files.readAllBytes(file);
21
22        return ResponseEntity.ok()
23            .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"download.bin\"")
24            .contentType(MediaType.APPLICATION_OCTET_STREAM)
25            .body(content);
26    }
27}

For huge files, you would normally avoid materializing the whole response as a byte[]. The example is only meant to show the flow from Feign response to controller response.

Common Pitfalls

The most common mistake is forgetting to close the response body. A leaked stream usually means leaked HTTP connections as well.

Another pitfall is reading the entire payload into memory by habit. If the whole reason for using a stream is file size, do not immediately call readAllBytes() in the service layer.

A third pitfall is declaring InputStream directly in the Feign interface and assuming Feign will manage the lifecycle exactly the way you want. It is possible to make that work in some setups, but it is harder to reason about and usually not worth the fragility.

Timeouts also matter. Large downloads often need explicit Feign connect and read timeout tuning, otherwise the stream logic is correct but the call still fails under real network conditions.

Summary

  • In Spring Feign, returning feign.Response is the most reliable way to access an InputStream
  • Open the stream with response.body().asInputStream() and close it with try-with-resources
  • Use Resource when you want a higher-level Spring abstraction and the payload is manageable
  • Avoid reading large responses fully into memory unless that is a deliberate choice
  • Treat stream handling and timeout configuration as part of the same download design

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.