Unravel › Java concepts › APIs and web
Streaming responses and Server-Sent Events
Instead of answering once, a streaming endpoint keeps the HTTP connection open and sends several pieces over time: progress updates, live events, or a big file written as it is generated.
Learn these first
At a glance
A radio broadcast versus a letter.
- 1A method returning SseEmitter keeps the connection openOther code (a listener, a scheduled task) calls emitter.send(...) whenever there is news, and complete() at the end. The browser reads it with EventSource.
- 2StreamingResponseBody writes a large body (CSV export, file download) straight to the output stream, so the whole content never sits in memory
- 3DeferredResult and Callable free the servlet thread while a long operation is pending: the result is set from another thread and the response is completed later
- 4In WebFlux, return a Flux with media type text/event-stream and the framework does the same with backpressure
- Keep a registry of live emitters and remove them on onCompletion, onTimeout and onError, otherwise closed browser tabs leak memory and every broadcast fails for them
- Each open stream holds a connection (and in blocking MVC, sometimes a thread)
- Servlet async support (request.startAsync()) detaches the request from its container thread; the response is committed and flushed incrementally
@GetMapping("/orders/{id}/progress")public SseEmitter progress(@PathVariable String id) { SseEmitter emitter = new SseEmitter(60_000L); emitters.put(id, emitter); emitter.onCompletion(() -> emitters.remove(id)); emitter.onTimeout(() -> emitters.remove(id)); return emitter;}- line 2 uses SseEmitter, the core of Streaming responses and Server-Sent Events
- line 3 uses SseEmitter, the core of Streaming responses and Server-Sent Events
Read the full explanation (about 1 min)
Instead of answering once, a streaming endpoint keeps the HTTP connection open and sends several pieces over time: progress updates, live events, or a big file written as it is generated.
A mental model
A radio broadcast versus a letter. The letter arrives complete and the exchange is over; the broadcast stays tuned and keeps delivering.
How it works
A method returning SseEmitter keeps the connection open. Other code (a listener, a scheduled task) calls emitter.send(...) whenever there is news, and complete() at the end. The browser reads it with EventSource.
StreamingResponseBody writes a large body (CSV export, file download) straight to the output stream, so the whole content never sits in memory.
DeferredResult and Callable free the servlet thread while a long operation is pending: the result is set from another thread and the response is completed later.
In WebFlux, return a Flux with media type text/event-stream and the framework does the same with backpressure.
Going deeper
Keep a registry of live emitters and remove them on onCompletion, onTimeout and onError, otherwise closed browser tabs leak memory and every broadcast fails for them.
Each open stream holds a connection (and in blocking MVC, sometimes a thread). Thousands of idle streams need either WebFlux, virtual threads or a dedicated gateway.
Proxies and load balancers close idle connections, so send a small heartbeat comment every 15 to 30 seconds and let clients reconnect with Last-Event-ID.
Under the hood
Servlet async support (request.startAsync()) detaches the request from its container thread; the response is committed and flushed incrementally. Spring wraps this in ResponseBodyEmitter, which buffers sends until the handler returns and then writes directly.
Common mistakes
- Never removing finished emitters.
- Sending to an emitter from several threads without synchronisation.
- No heartbeat so proxies cut the stream.
- Loading a whole export into memory instead of streaming it.
How Unravel recognises it
When you paste code, Unravel looks for these signs of streaming responses and server-sent events. Spotting them yourself is a good habit when you read unfamiliar code:
- Types:
SseEmitterResponseBodyEmitterStreamingResponseBodyDeferredResultServerSentEventAsyncRequestTimeoutExceptionWebAsyncTask - Configuration keys:
text/event-stream - Constants:
MediaType.TEXT_EVENT_STREAM_VALUEMediaType.APPLICATION_NDJSON_VALUE
Part of a learning path
- Building and documenting APIs · previous: Declarative HTTP clients (Feign, HTTP interfaces) · next: WebSockets and STOMP messaging
Related concepts
Frequently asked questions
What is Streaming responses and Server-Sent Events?
Instead of answering once, a streaming endpoint keeps the HTTP connection open and sends several pieces over time: progress updates, live events, or a big file written as it is generated.
What are the most common mistakes with Streaming responses and Server-Sent Events?
Never removing finished emitters. Sending to an emitter from several threads without synchronisation. No heartbeat so proxies cut the stream. Loading a whole export into memory instead of streaming it.
How can I see Streaming responses and Server-Sent Events in real code?
Paste your own Java into Unravel and it will point out where streaming responses and server-sent events appears, explain how it works at the detail level you choose, and draw the flow of the code around it.