Master Real-Time Data Streaming in Spring Boot 3: StreamingResponseBody vs SSE

This article compares Spring Boot 3's StreamingResponseBody and SseEmitter approaches for server‑side streaming, providing complete backend and frontend implementations, code samples, and performance considerations to help developers choose the right solution for real‑time data delivery.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Master Real-Time Data Streaming in Spring Boot 3: StreamingResponseBody vs SSE

When building real‑time web applications, the traditional request‑response model falls short for continuous data push. Spring Boot offers three server‑side streaming options: StreamingResponseBody for low‑level stream control, SseEmitter following the Server‑Sent Events (SSE) standard, and WebSocket for full duplex communication. This article focuses on StreamingResponseBody and SseEmitter, showing their backend and frontend implementations and highlighting their differences.

Environment

Spring Boot 3.4.2

1. Introduction

StreamingResponseBody provides high flexibility by writing directly to the output stream, suitable for large file downloads or custom stream transmission. SseEmitter implements the SSE protocol, offering a lightweight, HTTP‑based, browser‑native one‑way push mechanism ideal for notifications and log streams. WebSocket delivers powerful full‑duplex communication but incurs higher overhead.

2. Practical Example – StreamingResponseBody

Backend implementation using ResponseEntity<StreamingResponseBody>:

private final ObjectMapper objectMapper;

public UserStreamingController(ObjectMapper objectMapper) {
    this.objectMapper = objectMapper;
}

@GetMapping("/stream")
public ResponseEntity<StreamingResponseBody> streamUsers() {
    StreamingResponseBody responseBody = os -> {
        datas.forEach(user -> {
            try {
                String json = this.objectMapper.writeValueAsString(user) + "
";
                os.write(json.getBytes());
                os.flush();
                // simulate delay
                TimeUnit.MILLISECONDS.sleep(500);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        });
    };
    return ResponseEntity.ok()
        .header("Content-Type", "text/plain;charset=utf-8")
        .body(responseBody);
}

Frontend JavaScript to read the stream:

async function fetchStream() {
    const response = await fetch('/users/stream');
    if (!response.body) {
        console.error('ReadableStream not supported');
        return;
    }
    const container = document.querySelector('#stream_data');
    container.replaceChildren();
    const reader = response.body.getReader();
    const decoder = new TextDecoder('utf-8');
    let buffer = '';
    while (true) {
        const {done, value} = await reader.read();
        if (done) {
            console.log('Stream complete');
            break;
        }
        const chunk = decoder.decode(value, {stream: true});
        buffer += chunk;
        const lines = buffer.split('
');
        buffer = lines.pop(); // keep incomplete line
        lines.forEach(line => {
            if (line) {
                appendData(container, line);
            }
        });
    }
    if (buffer) {
        appendData(container, buffer);
    }
}

function appendData(container, data) {
    const li = document.createElement('li');
    li.textContent = data;
    container.appendChild(li);
}

Simple HTML page:

<body>
  <button type="button" onclick="fetchStream()">获取数据</button>
  <button type="button" onclick="fetchSse()">SSE获取数据</button>
  <hr/>
  <ul id="stream_data"></ul>
</body>

Resulting effect:

StreamingResponseBody effect
StreamingResponseBody effect

3. Practical Example – SseEmitter (SSE)

Backend implementation using SseEmitter:

@GetMapping(value = "/sse", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter sseUsers() {
    SseEmitter emitter = new SseEmitter(Long.MAX_VALUE);
    Executors.newSingleThreadExecutor().submit(() -> {
        try {
            for (User user : datas) {
                try {
                    String json = objectMapper.writeValueAsString(user);
                    emitter.send(SseEmitter.event()
                        .id(UUID.randomUUID().toString())
                        .name("user-data")
                        .data(json)
                        .reconnectTime(5000));
                    Thread.sleep(300);
                } catch (JsonProcessingException e) {
                    emitter.send(SseEmitter.event().name("error").data("JSON error: " + e.getMessage()));
                } catch (IOException e) {
                    emitter.completeWithError(e);
                    return;
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    emitter.completeWithError(e);
                    return;
                }
            }
            emitter.send(SseEmitter.event().name("complete").data("done").id(UUID.randomUUID().toString()));
            emitter.complete();
        } catch (Exception e) {
            emitter.completeWithError(e);
        }
    });
    return emitter;
}

Frontend JavaScript using EventSource:

async function fetchSse() {
    const container = document.querySelector('#stream_data');
    container.replaceChildren();
    const eventSource = new EventSource('/users/sse');
    eventSource.onopen = () => console.log('SSE connection opened');
    eventSource.addEventListener('user-data', event => {
        appendData(container, `${event.data}`);
    });
    eventSource.addEventListener('complete', () => {
        appendData(container, '✅ 所有数据加载完毕');
        eventSource.close();
    });
    eventSource.onerror = event => {
        console.error('SSE error:', event);
        if (eventSource.readyState === EventSource.CLOSED) {
            console.log('Connection closed');
        }
    };
}

Resulting effect:

SSE effect
SSE effect

Both implementations demonstrate how Spring Boot can push real‑time data to the browser, with StreamingResponseBody offering low‑level control and SseEmitter providing a standards‑based, lightweight solution.

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.

Real-time StreamingBackend DevelopmentServer-Sent EventsJava backendStreamingResponseBodySseEmitter
Spring Full-Stack Practical Cases
Written by

Spring Full-Stack Practical Cases

Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.

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.