Master Asynchronous Streaming in Spring: ResponseBodyEmitter, SseEmitter & StreamingResponseBody
This article explains how to handle time‑consuming Spring endpoints by using asynchronous streaming tools—ResponseBodyEmitter, SseEmitter, and StreamingResponseBody—detailing their use cases, code examples, and how they improve system responsiveness without blocking servlet threads.
When an interface takes a long time to process, traditional async methods like Callable, WebAsyncTask, DeferredResult or CompletableFuture can return only a single result, which is insufficient for scenarios that need continuous client updates.
Spring provides three streaming utilities— ResponseBodyEmitter, SseEmitter and StreamingResponseBody —that allow asynchronous processing while keeping the servlet thread free.
ResponseBodyEmitter
ResponseBodyEmitteris suitable for dynamically generating content and sending it piece‑by‑piece to the client, such as file‑upload progress or real‑time logs.
Typical usage creates a ResponseBodyEmitter instance, runs a time‑consuming task asynchronously, and calls send repeatedly.
@GetMapping("/bodyEmitter")
public ResponseBodyEmitter handle() {
ResponseBodyEmitter emitter = new ResponseBodyEmitter(-1L);
CompletableFuture.runAsync(() -> {
try {
for (int i = 0; i < 10000; i++) {
System.out.println("bodyEmitter " + i);
emitter.send("bodyEmitter " + i + " @ " + new Date() + "
");
Thread.sleep(2000);
}
emitter.complete();
} catch (Exception e) {
emitter.completeWithError(e);
}
});
return emitter;
}The endpoint streams a message every two seconds, mimicking the incremental response style of GPT‑like services.
SseEmitter
SseEmitterextends ResponseBodyEmitter and is designed for Server‑Sent Events (SSE), enabling the server to push real‑time data such as notifications or status updates to the client.
Client‑side JavaScript establishes an EventSource connection and updates the page when messages arrive.
<body>
<div id="content" style="text-align: center;">
<h1>SSE 接收服务端事件消息数据</h1>
<div id="message">等待连接...</div>
</div>
<script>
let source = null;
let userId = 7777;
function setMessageInnerHTML(message) {
const messageDiv = document.getElementById("message");
const newParagraph = document.createElement("p");
newParagraph.textContent = message;
messageDiv.appendChild(newParagraph);
}
if (window.EventSource) {
source = new EventSource('http://127.0.0.1:9033/subSseEmitter/' + userId);
setMessageInnerHTML("连接用户=" + userId);
source.addEventListener('open', function(e) { setMessageInnerHTML("建立连接。。。"); }, false);
source.addEventListener('message', function(e) { setMessageInnerHTML(e.data); });
} else {
setMessageInnerHTML("你的浏览器不支持SSE");
}
</script>
</body>Server‑side, a persistent map stores SseEmitter objects keyed by user ID; the emitter can later send events.
private static final Map<String, SseEmitter> EMITTER_MAP = new ConcurrentHashMap<>();
@GetMapping("/subSseEmitter/{userId}")
public SseEmitter sseEmitter(@PathVariable String userId) {
log.info("sseEmitter: {}", userId);
SseEmitter emitterTmp = new SseEmitter(-1L);
EMITTER_MAP.put(userId, emitterTmp);
CompletableFuture.runAsync(() -> {
try {
SseEmitter.SseEventBuilder event = SseEmitter.event()
.data("sseEmitter" + userId + " @ " + LocalTime.now())
.id(String.valueOf(userId))
.name("sseEmitter");
emitterTmp.send(event);
} catch (Exception ex) {
emitterTmp.completeWithError(ex);
}
});
return emitterTmp;
}
@GetMapping("/sendSseMsg/{userId}")
public void sseEmitter(@PathVariable String userId, String msg) throws IOException {
SseEmitter sseEmitter = EMITTER_MAP.get(userId);
if (sseEmitter == null) {
return;
}
sseEmitter.send(msg);
}Sending a request to /sendSseMsg/7777?msg=欢迎关注-->程序员小富 pushes the message to the connected browser instantly.
SSE connections automatically reconnect after a server restart.
StreamingResponseBody
StreamingResponseBodyis designed for large or continuous data streams, writing directly to an OutputStream without loading the entire payload into memory.
@GetMapping("/streamingResponse")
public ResponseEntity<StreamingResponseBody> handleRbe() {
StreamingResponseBody stream = out -> {
String message = "streamingResponse";
for (int i = 0; i < 1000; i++) {
try {
out.write(((message + i) + "
").getBytes());
out.write("
".getBytes());
out.flush();
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
return ResponseEntity.ok().contentType(MediaType.TEXT_HTML).body(stream);
}The demo streams simple text lines; the same approach works for large file downloads, preventing memory overflow.
Summary
Spring’s ResponseBodyEmitter, SseEmitter and StreamingResponseBody provide straightforward ways to build asynchronous streaming APIs, greatly enhancing performance and responsiveness for long‑running or real‑time use cases.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Su San Talks Tech
Su San, former staff at several leading tech companies, is a top creator on Juejin and a premium creator on CSDN, and runs the free coding practice site www.susan.net.cn.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
