Choosing Between SSE and WebSocket in Spring Boot: A Practical Guide
This article compares Server‑Sent Events, WebSocket, and long‑polling for real‑time data push in Spring Boot, explains their communication models, performance trade‑offs, and provides complete server‑side and client‑side code examples for implementing SSE and custom event types.
Overview
In web applications, common real‑time data push options include Server‑Sent Events (SSE), WebSocket, and long polling.
SSE is an HTTP‑based, one‑way server‑to‑client push technology that is simple, lightweight, and compatible with existing servers, but cannot handle client‑to‑server messages.
WebSocket provides full duplex communication, offering low latency and strong real‑time capabilities, though it requires server support for the protocol and is more complex to implement.
Long polling improves on short polling by keeping the request open until the server has data, reducing request frequency but still incurring latency when no new messages are available.
SSE vs WebSocket Comparison
SSE and WebSocket differ in several key aspects:
Communication model : SSE is one‑way (server to client); WebSocket is two‑way.
Connection handling : SSE relies on repeated HTTP requests or HTTP streaming; WebSocket establishes a single persistent connection after the handshake.
Real‑time performance : WebSocket offers lower latency and higher real‑time guarantees; SSE can be real‑time but depends on periodic server pushes.
Protocol : SSE runs over standard HTTP, supported by all servers; WebSocket uses a separate protocol requiring specific server support.
Complexity : SSE is lightweight and easy to implement; WebSocket is more complex.
Overall, SSE is ideal for lightweight, server‑to‑client scenarios, while WebSocket is suited for bidirectional, high‑frequency communication.
SSE Introduction
SSE (Server‑Sent Events) enables servers to push data to browsers over a persistent HTTP connection, delivering events as a text stream. It is simple to use, supports one‑way communication, and provides real‑time updates without frequent client polling.
Server‑Side Development (Spring Boot)
Dependencies
<code><dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency></code>Configuration (application.yml)
<code>spring:
mvc:
static-path-pattern: /**
web:
resources:
static-locations: classpath:/templates/</code>Controller
<code>@RestController
@RequestMapping("/sse")
public class SseController {
// Manage all client connections
private final Map<String, SseEmitter> sse = new ConcurrentHashMap<>();
// Create connection, produce text/event-stream
@GetMapping(path="/events/{id}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter createConnect(@PathVariable("id") String id) throws IOException {
SseEmitter emitter = new SseEmitter(0L);
sse.put(id, emitter);
emitter.onError(ex -> {
System.err.printf("userId: %s, error: %s%n", id, ex.getMessage());
sse.remove(id);
});
emitter.onCompletion(() -> {
sse.remove(id);
System.out.printf("%s, request completed...%n", id);
});
emitter.onTimeout(() -> System.err.println("timeout..."));
return emitter;
}
// Send a random message to a specific client
@GetMapping("/sender/{id}")
public String sender(@PathVariable("id") String id) throws Exception {
SseEmitter emitter = this.sse.get(id);
if (emitter != null) {
try {
emitter.send("Random message - " + new Random().nextInt(10000000));
} catch (Exception e) {
System.err.println(e.getMessage());
}
}
return "success";
}
}
</code>Front‑End Development
A minimal index.html page to receive SSE messages:
<code><html>
<head>
<title>SSE</title>
</head>
<body>
<button type="button" onclick="closeSse()">Close</button>
<hr/>
<ul id="list"></ul>
<script>
const evtSource = new EventSource(`/sse/events/${Date.now()}`);
evtSource.onmessage = event => {
const li = document.createElement('li');
li.innerHTML = "Received: " + event.data;
document.getElementById('list').appendChild(li);
};
evtSource.onopen = () => console.log('Connection opened...');
evtSource.onerror = e => console.error('Error:', e);
function closeSse() { evtSource.close(); }
</script>
</body>
</html>
</code>The above server and client code together implement a simple real‑time data push using SSE.
EventSource readyState values: 0 — connecting 1 — open 2 — closed
Custom Event Types
To send named events, modify the sender method:
<code>@GetMapping("/sender/{id}")
public String sender(@PathVariable("id") String id) throws Exception {
SseEmitter emitter = this.sse.get(id);
if (emitter != null) {
SseEventBuilder builder = SseEmitter.event();
builder.name("chat");
String msg = "Random message - " + new Random().nextInt(10000000);
builder.data(msg);
try { emitter.send(builder); } catch (Exception e) { e.printStackTrace(); }
}
return "success";
}
</code>And listen for the custom event on the client:
<code>evtSource.addEventListener("chat", event => {
const li = document.createElement('li');
li.innerHTML = "chat message: " + event.data;
document.getElementById('list').appendChild(li);
});
</code>Note: The default event type is "message"; only listeners for that type receive events without an explicit name.
End of tutorial.
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.
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.