java 104 lines · 3 tabs

Live Browser Notifications with Spring Boot SseEmitter and a Broadcaster Service

Shared by codesnips Aug 2026
3 tabs
package com.example.notifications;

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.IOException;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

@Component
public class SseBroadcaster {

    private static final long STREAM_TIMEOUT_MS = 30 * 60 * 1000L;

    private final List<SseEmitter> emitters = new CopyOnWriteArrayList<>();

    public SseEmitter subscribe() {
        SseEmitter emitter = new SseEmitter(STREAM_TIMEOUT_MS);
        emitters.add(emitter);
        emitter.onCompletion(() -> emitters.remove(emitter));
        emitter.onTimeout(() -> emitters.remove(emitter));
        emitter.onError(e -> emitters.remove(emitter));
        return emitter;
    }

    public void broadcast(String eventName, String eventId, Object payload) {
        for (SseEmitter emitter : emitters) {
            try {
                emitter.send(SseEmitter.event()
                        .id(eventId)
                        .name(eventName)
                        .data(payload));
            } catch (IOException ex) {
                emitter.completeWithError(ex);
            }
        }
    }

    public int activeConnections() {
        return emitters.size();
    }
}
3 files · java Explain with highlit

Server-Sent Events (SSE) provide a lightweight, one-directional streaming channel from server to browser over a single long-lived HTTP connection. Unlike WebSockets, SSE rides on plain HTTP, auto-reconnects natively via the browser's EventSource, and needs no extra protocol handshake, which makes it ideal for pushing notifications, progress updates, or activity feeds. This snippet shows the full server side: a broadcaster that owns the live connections, a controller that hands the browser a stream, and a service that publishes domain events onto it.

In SseBroadcaster, each subscriber is represented by a Spring SseEmitter stored in a CopyOnWriteArrayList. That collection is chosen deliberately: reads (iterating during a broadcast) vastly outnumber writes (subscribe/unsubscribe), and it avoids ConcurrentModificationException when an emitter is removed mid-iteration. The subscribe method registers cleanup callbacks — onCompletion, onTimeout, and onError all deregister the emitter so dead connections don't leak. A generous timeout is passed in so the browser holds the stream open rather than reconnecting every 30 seconds. Sending is wrapped in a try/catch: if a client has gone away, emitter.send throws IOException, and the code calls completeWithError to trigger the same cleanup path.

The NotificationController exposes GET /notifications/stream, which returns the raw SseEmitter produced by the broadcaster. Spring recognizes the return type and keeps the request thread's response open for streaming. Crucially it sends an initial event("connected") immediately so proxies and the browser confirm the stream is live before any real data arrives. The produces = TEXT_EVENT_STREAM_VALUE media type is what tells clients and intermediaries this is an SSE response.

In NotificationService, business logic like notifyUser builds a Notification payload and delegates to the broadcaster. Named events (.name("notification")) let the front-end register targeted addEventListener handlers instead of a generic catch-all. Providing a stable .id(...) lets the browser send Last-Event-ID on reconnect so a more advanced implementation could replay missed messages.

The main pitfalls to remember: SSE is unidirectional, most browsers cap concurrent connections per domain, and a load-balanced deployment needs sticky sessions or an external fan-out (Redis pub/sub) because emitters live in one JVM's memory. For a single service pushing to modest numbers of clients, this pattern is simple and robust.


Related snips

Share this code

Here's the card — post it anywhere.

Live Browser Notifications with Spring Boot SseEmitter and a Broadcaster Service — share card
Link copied