Spring Async Streaming: ResponseBodyEmitter, SseEmitter, and StreamingResponseBody Explained

This article explains three Spring mechanisms for asynchronous streaming responses—ResponseBodyEmitter for progressive data sending, SseEmitter for server-sent events with auto-reconnect, and StreamingResponseBody for large file streaming—with code examples and use cases for each.

Su San Talks Tech
Su San Talks Tech
Su San Talks Tech
Spring Async Streaming: ResponseBodyEmitter, SseEmitter, and StreamingResponseBody Explained

ResponseBodyEmitter

ResponseBodyEmitter

is suitable for scenarios where content is generated dynamically and sent to the client incrementally, such as file upload progress or real-time logs. It allows sending updates to the client during task execution.

For example, when using GPT, the answer appears gradually rather than all at once, creating a more natural interaction experience.

Note: If ResponseBodyEmitter timeout is set to 0 or -1 , the connection will not time out; otherwise, the default timeout will close the connection. The same applies to the other two tools.
@GetMapping("/bodyEmitter")
public ResponseBodyEmitter handle() {
    // Create a ResponseBodyEmitter, -1 means no timeout
    ResponseBodyEmitter emitter = new ResponseBodyEmitter(-1L);
    // Execute time-consuming operation asynchronously
    CompletableFuture.runAsync(() -> {
        try {
            // Simulate time-consuming operation
            for (int i = 0; i < 10000; i++) {
                System.out.println("bodyEmitter " + i);
                // Send data
                emitter.send("bodyEmitter " + i + " @ " + new Date() + "
");
                Thread.sleep(2000);
            }
            // Complete
            emitter.complete();
        } catch (Exception e) {
            // End with error on exception
            emitter.completeWithError(e);
        }
    });
    return emitter;
}

The implementation is straightforward. By simulating a response every 2 seconds, the page shows data being generated dynamically, similar to GPT's streaming output.

ResponseBodyEmitter streaming demo
ResponseBodyEmitter streaming demo

SseEmitter

SseEmitter

is a subclass of ResponseBodyEmitter and also enables dynamic content generation. It is primarily used for server-to-client real-time data push, such as real-time message push and status updates. A previous article details Server-Sent Events (SSE) technology.

SSE architecture diagram
SSE architecture diagram

SSE opens a one-way channel between server and client. The server responds with a text/event-stream data stream instead of a one-time data packet, pushing updates to the client whenever data changes.

SSE data flow
SSE data flow

The concept resembles online video streaming: a continuous flow of data to the browser, akin to a long download.

On the client side, a single HTTP request establishes a connection and waits for messages. The server creates an SseEmitter object for each connection and sends messages through this channel.

<body>
<div id="content" style="text-align: center;">
<h1>SSE Receive Server-Sent Event Messages</h1>
<div id="message">Waiting for connection...</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) {
    // Establish connection
    source = new EventSource('http://127.0.0.1:9033/subSseEmitter/' + userId);
    setMessageInnerHTML("Connected user=" + userId);
    /**
     * Connection established triggers open event
     * Alternative: source.onopen = function (event) {}
     */
    source.addEventListener('open', function (e) {
        setMessageInnerHTML("Connection established...");
    }, false);
    /**
     * Client receives data from server
     * Alternative: source.onmessage = function (event) {}
     */
    source.addEventListener('message', function (e) {
        setMessageInnerHTML(e.data);
    });
} else {
    setMessageInnerHTML("Your browser does not support SSE");
}
</script>
</body>

On the server side, we persist the SseEmitter sender objects so that when a message occurs, we can retrieve the corresponding emitter and call send to push.

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 message to userId=7777 via 127.0.0.1:9033/sendSseMsg/7777?msg=Welcome displays the message in real time on the page.

SSE real-time message demo
SSE real-time message demo

Moreover, SSE supports auto-reconnect : once a connection is established, even if the server restarts, the client can automatically reconnect.

SSE auto-reconnect demonstration
SSE auto-reconnect demonstration

StreamingResponseBody

StreamingResponseBody

differs slightly from the other response handlers. It is mainly used for transmitting large data volumes or continuous data streams, supporting writing data directly to an OutputStream.

For instance, when downloading a very large file, StreamingResponseBody avoids loading the entire file into memory. Instead, it continuously streams the file to the client, solving the common memory overflow issue when downloading large files.

The interface returns a StreamingResponseBody object, writes data to the output stream, and flushes. Each flush call writes data to the client once.

@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());
                // Each flush writes data to the frontend once
                out.flush();
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    };
    return ResponseEntity.ok().contentType(MediaType.TEXT_HTML).body(stream);
}

The demo outputs a simple text stream; for file downloads, converting to a file stream works the same way.

StreamingResponseBody streaming demo
StreamingResponseBody streaming demo

Summary

This article introduces three tools for implementing asynchronous streaming interfaces in Spring. They are simple to use and have no major pitfalls, but their application scenarios in real business are extensive. Through these tools, system performance and responsiveness can be effectively improved.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

SpringServer-Sent EventsSSEJava backendResponseBodyEmitterStreamingResponseBodySseEmitterasync streaming
Su San Talks Tech
Written by

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.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.