Using Spring's ResponseBodyEmitter for Real-Time Log Streaming
This article introduces Spring Framework's ResponseBodyEmitter, explains its role in asynchronous HTTP responses, outlines typical use cases such as long polling and real‑time log streaming, provides a complete Spring Boot example with code, and compares it with SSE and raw OutputStream approaches.
ChatGPT's popularity has brought streaming output technology into the public eye, and many developers have started exploring Server‑Sent Events (SSE) asynchronous handling. This article presents a simpler way to achieve streaming output using ResponseBodyEmitter , a feature introduced in Spring Framework 4.2.
What is ResponseBodyEmitter
Compared with SSE, ResponseBodyEmitter is easier to use. It is mainly used for asynchronous HTTP responses, allowing data to be sent to the client step‑by‑step instead of all at once, which is ideal for long‑running or streaming scenarios. Note that ResponseBodyEmitter is essentially an interface.
Typical Use Cases
Long polling: The server responds immediately when data is available; otherwise it keeps the connection open.
Server‑Sent Events (SSE): The server continuously pushes events to the client for real‑time interaction.
Streaming transfer: Large data can be sent chunk by chunk, suitable for file downloads or real‑time data streams.
Asynchronous processing: Allows incremental return of results for time‑consuming tasks, improving user experience.
Business Scenario Example
In practice, ResponseBodyEmitter can be used for real‑time progress bar updates, chat, stock price updates, system log streaming, and AI streaming responses.
Real‑Time Log Streaming Demo
Below is a simple Spring Boot controller that uses ResponseBodyEmitter to stream server logs in real time.
Creating the Controller
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;
@RestController
@RequestMapping("/api/log")
public class LogController {
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public ResponseBodyEmitter streamLogs() {
ResponseBodyEmitter emitter = new ResponseBodyEmitter();
// Start async thread to process data and send
new Thread(() -> {
try {
while (true) {
String logEntry = getLatestLogEntry();
if (logEntry != null) {
emitter.send(logEntry);
}
// Check log updates every second
Thread.sleep(1000);
}
} catch (Exception e) {
// End response with error if exception occurs
emitter.completeWithError(e);
}
}).start();
return emitter;
}
private String getLatestLogEntry() {
// Simulate fetching the latest log entry from a file
return "2025-02-12 12:00:00 - INFO: User logged in successfully.";
}
}Running Effect
When the application is started and the /api/log/stream endpoint is accessed, the client receives a continuously updating log stream, with the server pushing a new log entry every second.
Core Methods of ResponseBodyEmitter
send(Object data): Sends data to the client; can be called multiple times for incremental transmission.
complete(): Ends the response stream, indicating all data has been sent.
onTimeout(Runnable callback): Sets a timeout callback that executes when the connection times out.
onCompletion(Runnable callback): Sets a completion callback that runs after all data is sent.
How ResponseBodyEmitter Works
Asynchronous Data Generation and Push
In the traditional request‑response model, the server waits until the entire response is generated before sending it. ResponseBodyEmitter breaks this pattern by allowing the server to generate and push data asynchronously as soon as a part is ready.
Chunked Transfer Mechanism
ResponseBodyEmitter uses HTTP chunked encoding. Instead of specifying Content‑Length upfront, the server sends data in independent chunks, each with its own length header, enabling the client to process data immediately upon receipt.
Connection Lifecycle Management
After all data is sent, complete() should be called to close the connection. If an error occurs, completeWithError() ends the response and forwards the error to the client, preventing resource leaks.
Precautions
Client support: Most modern browsers and HTTP client libraries support chunked transfer, but some legacy clients may have compatibility issues.
Timeout settings: To avoid long‑living connections, set a timeout, e.g. emitter.onTimeout(() -> emitter.complete());
Thread safety: The send() method is thread‑safe, but you must manage the lifecycle of background tasks to prevent resource leaks.
Connection closure: Always call complete() or completeWithError() when the task finishes; otherwise the connection may remain open and waste resources.
Comparison with Streaming and SSE
Streaming (OutputStream): Directly writes to the response stream, offering flexibility but requiring manual stream management.
SSE: Uses the text/event-stream protocol for server‑push events, suitable for specific push scenarios but requires client support.
ResponseBodyEmitter: Works with any HTTP client, integrates easily with Spring, and provides a convenient solution for streaming data.
For AI‑style streaming responses, ResponseBodyEmitter offers better HTTP compatibility than SSE while remaining lightweight.
Conclusion
ResponseBodyEmitter is a lightweight streaming solution provided by the Spring framework, significantly improving user experience in high‑concurrency and real‑time scenarios. By leveraging it, developers can effortlessly implement server‑to‑client real‑time data pushes such as progress updates, chat, stock monitoring, and system log streaming.
Java Architect Essentials
Committed to sharing quality articles and tutorials to help Java programmers progress from junior to mid-level to senior architect. We curate high-quality learning resources, interview questions, videos, and projects from across the internet to help you systematically improve your Java architecture skills. Follow and reply '1024' to get Java programming resources. Learn together, grow together.
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.