Spring Boot
RESTful API
Chunked Response
Streaming Data
@RestController

How To Stream Chunked Response With Spring Boot RestController

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

If you want a Spring Boot controller to send data progressively instead of buffering the entire response first, you need a streaming response type. In Spring MVC, the usual choices are StreamingResponseBody, ResponseBodyEmitter, or SseEmitter, depending on the protocol and payload style. For ordinary chunked HTTP output, StreamingResponseBody is often the clearest starting point.

Why Chunked Streaming Helps

Chunked responses are useful when:

  • the response is large
  • data becomes available incrementally
  • the client should start processing before the full payload exists
  • you want to avoid holding the whole response body in memory

This is common for report export, log streaming, generated text, and long-running downloads.

Basic StreamingResponseBody Example

StreamingResponseBody lets you write directly to the response output stream.

java
1import java.nio.charset.StandardCharsets;
2import org.springframework.http.MediaType;
3import org.springframework.web.bind.annotation.GetMapping;
4import org.springframework.web.bind.annotation.RestController;
5import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
6
7@RestController
8public class StreamController {
9
10    @GetMapping(value = "/stream", produces = MediaType.TEXT_PLAIN_VALUE)
11    public StreamingResponseBody stream() {
12        return outputStream -> {
13            for (int i = 1; i <= 5; i++) {
14                String chunk = "chunk " + i + "\n";
15                outputStream.write(chunk.getBytes(StandardCharsets.UTF_8));
16                outputStream.flush();
17                Thread.sleep(500);
18            }
19        };
20    }
21}

Each flush gives the client a chance to receive the next chunk immediately instead of waiting for the whole loop to finish.

ResponseBodyEmitter for Higher-Level Emission

If you prefer sending serializable objects or text chunks through a higher-level API, use ResponseBodyEmitter.

java
1import org.springframework.web.bind.annotation.GetMapping;
2import org.springframework.web.bind.annotation.RestController;
3import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;
4
5@RestController
6public class EmitterController {
7
8    @GetMapping("/emitter")
9    public ResponseBodyEmitter emitter() {
10        ResponseBodyEmitter emitter = new ResponseBodyEmitter();
11
12        new Thread(() -> {
13            try {
14                for (int i = 1; i <= 5; i++) {
15                    emitter.send("message " + i + "\n");
16                    Thread.sleep(500);
17                }
18                emitter.complete();
19            } catch (Exception e) {
20                emitter.completeWithError(e);
21            }
22        }).start();
23
24        return emitter;
25    }
26}

This is convenient, but you still need to think about executor management and error handling.

When to Use SseEmitter Instead

If the client expects Server-Sent Events rather than generic chunked text, use SseEmitter. That gives you the text/event-stream protocol and event semantics.

Use SseEmitter for browser event streams. Use StreamingResponseBody or ResponseBodyEmitter for generic chunked responses.

Buffering Can Defeat Streaming

A common surprise is that the code flushes, but the client still receives everything at once. That can happen because of:

  • reverse proxies buffering the response
  • servlet container buffering
  • the client reading only after connection close
  • response compression changing the behavior

So successful server code is only part of the story. The whole HTTP path has to allow streaming.

Keep the Work Off the Request Thread if Necessary

If each chunk depends on slow work, consider asynchronous execution or a dedicated executor. Blocking the request thread for a long stream may be acceptable in small systems, but it becomes a capacity problem under load.

The right design depends on expected concurrency and response duration.

Common Pitfalls

  • Returning a normal object or String and expecting Spring to send it as progressive chunks automatically.
  • Forgetting to flush the output stream, which can delay chunk delivery.
  • Choosing SseEmitter when the client really wants generic chunked HTTP, not event-stream semantics.
  • Testing only locally and missing buffering introduced by proxies or production infrastructure.
  • Starting background threads manually without thinking about thread management and error handling.

Summary

  • Use a streaming response type when you want to send data incrementally from a Spring controller.
  • 'StreamingResponseBody is the simplest option for generic chunked output.'
  • 'ResponseBodyEmitter is useful when you want higher-level chunk emission.'
  • 'SseEmitter is for Server-Sent Events, not generic streaming.'
  • Successful streaming depends on the full request path, including proxies and client behavior.

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.