Definition
- WebSocket – full-duplex, two-way communication over a single upgraded TCP connection; suited to interactive scenarios like chat, gaming, and collaborative editing.
- SSE (Server-Sent Events) – one-way server-to-client streaming over a single long-lived HTTP connection with built-in automatic reconnection; suited to notifications, tickers, and dashboards.
- Decision rule – default to SSE when the flow is purely server-push; reach for WebSocket only when real-time client-to-server messaging is genuinely needed.
- Occasional client action – if a mostly-one-way feature needs the client to act (e.g. acknowledge an alert), make that action a normal REST POST alongside the SSE stream.
At National Bank of India, a trader and the market-data desk need a two-way phone line where both shout simultaneously: that is WebSocket. The stock price board broadcasts to everyone watching: one-way, and if someone steps out and comes back the board is still showing: that is SSE. If all you need is to broadcast, the one-way loudspeaker is simpler and self-healing.
Understanding the Concepts
What is the defining difference?
The defining difference is directionality. WebSocket is full-duplex: after the HTTP upgrade handshake, both client and server send messages at any time, independently. SSE is half-duplex: the server streams events to the client, and the client cannot send on that channel.
WebSocket: Client ←──messages──→ Server (both ways) SSE: Client ←──events──── Server (server to client only)
When do you need WebSocket?
Use WebSocket when the interaction is genuinely two-way and interactive: a chat where both people type, a multiplayer game, collaborative document editing, or a trading terminal where orders go up and fills come down.
package com.bank.websocket;
import org.springframework.web.socket.*;
import org.springframework.web.socket.handler.TextWebSocketHandler;
public class TradingHandler extends TextWebSocketHandler {
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) {
Order order = parseOrder(message.getPayload());
Fill fill = matchEngine.execute(order);
session.sendMessage(new TextMessage(toJson(fill)));
// Two-way: client sends orders, server sends fills
}
}
When is SSE the better fit?
Use SSE when the flow is only server-to-client: live notifications, stock tickers, monitoring dashboards, progress updates. SSE runs over plain HTTP, passes through proxies and firewalls, and the browser's EventSource reconnects automatically.
package com.bank.controller;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
@GetMapping(value = "/api/v1/notifications/{userId}",
produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter notifications(@PathVariable String userId) {
SseEmitter emitter = new SseEmitter(0L);
notificationHub.subscribe(userId, event ->
emitter.send(SseEmitter.event().id(event.id()).data(event)));
return emitter;
}
// Browser: 3 lines, auto-reconnect, resume from last event ID:
const es = new EventSource('/api/v1/notifications/rajesh');
es.onmessage = (e) => showNotification(JSON.parse(e.data));
// If connection drops, EventSource reconnects and sends Last-Event-ID header
How do WebSocket and SSE compare? (comparison table)
| Feature | WebSocket | SSE | |---|---|---| | Direction | Full-duplex (both ways) | Server → client only | | Protocol | Upgraded TCP (ws/wss) | Plain HTTP | | Auto-reconnect | Manual | Built-in (EventSource) | | Resume from last event | Manual | Built-in (Last-Event-ID) | | Binary support | Yes | No (text only) | | Proxy friendliness | May be blocked | Passes through | | Infrastructure | WebSocket-aware required | Standard HTTP | | Per-message overhead | Very low (2-14 bytes) | Low (text framing) | | Complexity | Higher | Lower |
What about SSE's limitations?
SSE is text-only and cannot send binary data. Over HTTP/1.1, browsers limit concurrent SSE connections per domain (typically 6), which HTTP/2 multiplexing solves. And the client cannot send data back on the same channel.
// SSE limitation: client cannot send on the stream.
// If the client needs to acknowledge a notification, use a separate REST call:
@PostMapping("/api/v1/notifications/{id}/acknowledge")
public void acknowledge(@PathVariable String id) {
notificationService.markRead(id);
}
// The SSE stream stays one-way; the acknowledgment is a normal POST.
What about WebSocket's costs?
WebSocket needs WebSocket-aware load balancers and proxies. You must implement reconnection and heartbeats yourself. You lose HTTP caching. And you must manage the connection lifecycle for long-lived connections at scale.
// WebSocket: must implement heartbeat yourself:
@Scheduled(fixedRate = 30000)
public void heartbeat() {
sessions.forEach(session -> {
try {
session.sendMessage(new PingMessage());
} catch (IOException e) {
sessions.remove(session); // dead connection
}
});
}
What are common mistakes?
The most common mistake is using WebSocket for a one-way feed and then carrying the complexity of reconnection, heartbeats, and WebSocket-aware infrastructure for something SSE handles automatically.
// Wrong – WebSocket for one-way stock prices:
// Must implement: reconnection, heartbeats, lifecycle, load balancer config
// Correct – SSE for one-way stock prices:
@GetMapping(value = "/prices", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter prices() {
SseEmitter emitter = new SseEmitter(0L);
priceFeed.subscribe(price -> emitter.send(price));
return emitter;
}
// Auto-reconnect, proxy-friendly, 3 lines on the client
How do you handle the "mostly one-way" case?
If a feature is mostly server-push but needs an occasional client action (like acknowledging a notification), keep the SSE stream one-way and make the client action a normal REST call. This keeps the streaming channel simple.
SSE stream: Server ──notifications──→ Client (one-way) REST action: Client ──POST /ack──→ Server (separate call) // Simple and clean – no need for WebSocket
What should a senior engineer know?
A senior engineer defaults to SSE for server-push and reaches for WebSocket only when the client must push in real time. They configure SSE event IDs so the browser can resume from the last received event after reconnection. They set SseEmitter timeout to 0L for indefinite streams and handle onCompletion and onError callbacks to clean up subscriptions. In banking, notifications, tickers, and dashboards are overwhelmingly one-way, while trading terminals and live chat support are two-way.
Visual Overview
Decision Rule:
Does the client need to send data back in real time?
├── YES → WebSocket
│ (chat, trading, collaboration)
│
└── NO → SSE
(notifications, tickers, dashboards)
If occasional client action needed:
use a separate REST POST alongside SSE
SSE: Client ←──events──── Server (auto-reconnect)
WebSocket: Client ←──messages──→ Server (manual reconnect)
Key Takeaway
WebSocket and SSE are both persistent-connection real-time techniques that differ in direction and complexity. WebSocket is full-duplex: after the upgrade handshake, both client and server send messages independently, which suits genuinely interactive two-way scenarios like chat, collaborative editing, and a trading terminal. SSE is one-way: the server streams events to the client over a single HTTP connection, which suits server-push-only scenarios like notifications, tickers, and dashboards.
The practical advantage of SSE when one-way suffices is significant. It runs over plain HTTP, passes through proxies and firewalls that sometimes block WebSocket upgrades, and the browser's EventSource reconnects automatically and resumes from the last event ID. WebSocket requires WebSocket-aware infrastructure, manual reconnection, manual heartbeats, and loses HTTP caching.
At National Bank of India, payment notifications and the stock-price ticker are one-way, so they run on SSE with automatic reconnection. The active trading terminal genuinely needs two-way messaging, so it uses WebSocket. When the notification feature needed client acknowledgment, the team did not complicate the SSE stream: the acknowledgment is a plain REST POST alongside the one-way stream.
The decision rule is simple: if the client must push in real time, use WebSocket; otherwise use SSE. If a mostly-one-way feature needs an occasional client action, make that action a REST call.
Remember one sentence: default to SSE for server push because it is simpler and self-healing; reach for WebSocket only when the client must talk back in real time.
---