What Is SSE? Why Is This 20‑Year‑Old Technology Suddenly Booming? – Interview Deep Dive

This article provides a comprehensive interview‑style analysis of Server‑Sent Events (SSE), covering its definition, 20‑year history, core features, data format, Java Spring Boot implementation, client‑side EventSource usage, detailed comparison with WebSocket and other polling techniques, and a decision guide for choosing the right real‑time communication protocol in modern AI‑driven applications.

Tech Freedom Circle
Tech Freedom Circle
Tech Freedom Circle
What Is SSE? Why Is This 20‑Year‑Old Technology Suddenly Booming? – Interview Deep Dive

What is Server‑Sent Events (SSE)

SSE is an HTTP‑based server‑push technology. The server streams text data to the browser over a single long‑lived HTTP connection. It is not a separate protocol; it extends the standard HTTP/HTTPS stack.

Unidirectional (server → client) communication.

Uses standard HTTP/HTTPS ports, so no extra firewall rules.

Browser‑native EventSource API provides automatic reconnection and retry logic.

Supports custom event names ( event) and identifiers ( id) for reliable resume after a disconnect.

Historical evolution

Early 2000s – short polling, long polling, Flash hacks, and prototype WebSocket solutions were used for real‑time updates.

2006‑2008 – WHATWG and W3C standardized the text/event-stream MIME type and the SSE format.

2011 onward – major browsers (Firefox 6, Chrome 6, Safari 5, Opera 11.5) added native EventSource support. Internet Explorer never supported SSE.

2022‑present – explosion of generative‑AI applications (ChatGPT, Claude, etc.) that stream token‑by‑token output, making SSE the natural choice for “typing‑machine” style streaming.

Comparison with other real‑time techniques

Polling : client repeatedly sends short HTTP requests. Simple but wasteful and high latency.

Long polling : client holds a request open until the server has data, then immediately re‑issues another request. Reduces empty requests but still incurs per‑request overhead.

SSE : server keeps a single HTTP connection open and streams text events. Low overhead, automatic reconnection, ideal for pure‑text streams.

WebSocket : full‑duplex binary‑capable protocol that requires an HTTP upgrade handshake. More complex and may be blocked by restrictive proxies.

Working process (client → server)

Browser sends a normal GET request with Accept: text/event-stream.

Server responds with Content-Type: text/event-stream, Cache-Control: no-cache, and Connection: keep-alive, then keeps the TCP connection open.

Server streams events using lines such as event: message and data: …, ending each event with a double newline ( \n\n).

Browser receives events via the native EventSource object, which automatically reconnects on failure and sends the last received id in the Last-Event-ID header.

Server‑side implementation details

Required HTTP response headers:

Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive

Core fields that may appear in each event:

data : payload (plain text or JSON).

event : custom event name for addEventListener handling.

id : unique identifier used for reconnection resume.

retry : reconnection interval in milliseconds (default ~3 s).

Example server message:

event: order
id: 1001
data: {"orderId":"20230501","status":"paid"}

Spring Boot real‑world example

The following minimal Spring Boot controller demonstrates SSE broadcasting, a progress task, and custom events.

package com.example.sse.controller;

import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.IOException;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@RestController
public class SseController {
    // Thread‑safe list of active emitters
    private final CopyOnWriteArrayList<SseEmitter> emitters = new CopyOnWriteArrayList<>();
    // Executor for asynchronous sending
    private final ExecutorService executor = Executors.newCachedThreadPool();

    /** Client subscribes to SSE */
    @GetMapping(value = "/sse/subscribe", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter subscribe() {
        SseEmitter emitter = new SseEmitter(Long.MAX_VALUE); // disable default timeout
        emitters.add(emitter);
        emitter.onCompletion(() -> emitters.remove(emitter));
        emitter.onTimeout(() -> emitters.remove(emitter));
        try {
            emitter.send(SseEmitter.event()
                .name("CONNECTED")
                .data("You are successfully connected to SSE server!")
                .reconnectTime(5000));
        } catch (IOException e) {
            emitter.completeWithError(e);
        }
        return emitter;
    }

    /** Broadcast a message to all connected clients */
    @GetMapping("/sse/broadcast")
    public String broadcastMessage(@RequestParam String message) {
        executor.execute(() -> {
            for (SseEmitter emitter : emitters) {
                try {
                    emitter.send(SseEmitter.event()
                        .name("BROADCAST")
                        .data(message)
                        .id(String.valueOf(System.currentTimeMillis())));
                } catch (IOException e) {
                    emitters.remove(emitter);
                    emitter.completeWithError(e);
                }
            }
        });
        return "Broadcast message: " + message;
    }

    /** Simulate a long‑running task and push progress */
    @GetMapping("/sse/start-task")
    public String startTask() {
        executor.execute(() -> {
            try {
                for (int i = 0; i <= 100; i += 10) {
                    Thread.sleep(1000);
                    for (SseEmitter emitter : emitters) {
                        try {
                            emitter.send(SseEmitter.event()
                                .name("PROGRESS")
                                .data(i + "% completed")
                                .id("task-progress"));
                        } catch (IOException e) {
                            emitters.remove(emitter);
                        }
                    }
                    if (i == 100) {
                        for (SseEmitter emitter : emitters) {
                            try {
                                emitter.send(SseEmitter.event()
                                    .name("COMPLETE")
                                    .data("Task completed successfully!"));
                            } catch (IOException e) {
                                emitters.remove(emitter);
                            }
                        }
                    }
                }
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });
        return "Task started!";
    }
}

A simple PageController returns the HTML client page (e.g., sse-client.html) that creates an EventSource, registers listeners for CONNECTED, BROADCAST, PROGRESS, and COMPLETE, and provides UI buttons for connect, disconnect, broadcast, and start‑task.

Typical use cases

AI token‑by‑token streaming (ChatGPT, Claude, etc.).

Real‑time notifications such as order updates or system alerts.

Live dashboards for stock quotes, IoT sensor data, or log tailing.

Choosing between SSE and WebSocket

Direction : Use SSE for server‑to‑client only; choose WebSocket for full‑duplex interaction.

Complexity : SSE requires only a few lines of server code and a native browser API. WebSocket needs an upgrade handshake, heartbeat handling, and frame parsing.

Reconnection : SSE has built‑in automatic reconnection and resume via Last-Event-ID. WebSocket must implement this manually.

Binary support : WebSocket handles binary frames; SSE is text‑only.

Network compatibility : SSE works through most firewalls and proxies because it is plain HTTP. WebSocket may be blocked in restrictive environments.

For most AI‑driven streaming scenarios, SSE is the default choice. If the application also needs bidirectional interaction, binary transfer, or ultra‑low latency (e.g., multiplayer games, collaborative editors), a hybrid approach can be used.

Hybrid SSE + WebSocket architecture

The client first probes network quality. If round‑trip time or packet‑loss exceeds thresholds, it falls back to SSE; otherwise it establishes a WebSocket. The HybridConnection class abstracts sending: WebSocket messages are sent directly, while SSE messages are delivered via a separate HTTP POST endpoint.

class NetworkProbe {
    static RTT_THRESHOLD = 300; // ms
    static PACKET_LOSS_THRESHOLD = 0.2; // 20%
    async check() {
        const start = Date.now();
        try {
            await fetch('/ping', {cache: 'no-store'});
            const rtt = Date.now() - start;
            return {isWeak: rtt > NetworkProbe.RTT_THRESHOLD};
        } catch {
            return {isWeak: true};
        }
    }
}

class HybridConnection {
    constructor() {
        this.currentProtocol = null; // 'ws' or 'sse'
        this.ws = null;
        this.sse = null;
        this.messageQueue = [];
    }
    async connect() {
        const {isWeak} = await new NetworkProbe().check();
        this.currentProtocol = isWeak ? 'sse' : 'ws';
        if (this.currentProtocol === 'ws') this._initWebSocket();
        else this._initSSE();
    }
    _initWebSocket() {
        this.ws = new WebSocket('wss://api.example.com');
        this.ws.onmessage = this._handleMessage;
        this.messageQueue.forEach(m => this.ws.send(m));
        this.messageQueue = [];
    }
    _initSSE() {
        this.sse = new EventSource('https://api.example.com/sse');
        this.sse.onmessage = this._handleMessage;
    }
    _handleMessage = (event) => {
        // unified business logic
        console.log('Received:', event.data);
    };
    send(data) {
        const payload = JSON.stringify(data);
        if (this.currentProtocol === 'ws' && this.ws?.readyState === 1) {
            this.ws.send(payload);
        } else if (this.currentProtocol === 'sse') {
            fetch('/send', {method: 'POST', body: payload});
        } else {
            this.messageQueue.push(payload);
        }
    }
    async switchProtocol() {
        const {isWeak} = await new NetworkProbe().check();
        if (isWeak && this.currentProtocol === 'ws') {
            this.ws?.close();
            this._initSSE();
            this.currentProtocol = 'sse';
        } else if (!isWeak && this.currentProtocol === 'sse') {
            this.sse?.close();
            this._initWebSocket();
            this.currentProtocol = 'ws';
        }
    }
}

Conclusion

SSE provides a lightweight, standards‑based solution for unidirectional real‑time data streams. Its simplicity, automatic reconnection, and excellent HTTP compatibility make it the go‑to choice for text‑only server‑push scenarios such as AI token streaming, notifications, and live dashboards. When bidirectional communication, binary payloads, or ultra‑low latency are required, WebSocket—or a hybrid SSE + WebSocket design—should be considered.

Javareal-timeAISpring Bootserver-sent-eventsSSEwebsockets
Tech Freedom Circle
Written by

Tech Freedom Circle

Crazy Maker Circle (Tech Freedom Architecture Circle): a community of tech enthusiasts, experts, and high‑performance fans. Many top‑level masters, architects, and hobbyists have achieved tech freedom; another wave of go‑getters are hustling hard toward tech freedom.

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.