Server-Sent Events
Server-Sent Events
Server-Sent Events (SSE) is a server-push technology built on top of HTTP that allows a server to send a unidirectional stream of events to a client over a long-lived HTTP connection. The browser's native EventSource API manages the connection, including automatic reconnection.
Core Characteristics
- Direction: server to client only (unidirectional)
- Protocol: standard HTTP/1.1 or HTTP/2 — no protocol upgrade required
- Format:
text/event-streamMIME type; each event is a block offield: valuelines separated by blank lines - Reconnection: automatic — the browser
EventSourcereconnects after connection loss using theLast-Event-IDheader to resume from the last received event - Authentication: works naturally with session cookies (
withCredentials: true) — aligned with the BFF-For-SPA pattern
SSE vs WebSocket
| Aspect | SSE | WebSocket |
|---|---|---|
| Direction | Server → Client only | Bidirectional |
| Protocol | HTTP/1.1 or HTTP/2 | HTTP upgrade to ws:// or wss:// |
| Spring support | Flux<ServerSentEvent<T>> | Spring WebSocket + STOMP broker |
| Angular support | EventSource API | WebSocket API or RxJS wrapper |
| BFF complexity | Low — expose a reactive Flux endpoint | High — BFF must proxy WS or implement STOMP broker relay |
| Use cases | Notifications, live feeds, progress bars | Chat, collaborative editing, multiplayer |
| Reconnection | Automatic (browser-managed) | Manual (application must implement) |
| Cookie auth | Works — same HTTP connection | More complex with session cookies |
| Firewall traversal | Usually fine — standard HTTP | Sometimes blocked by enterprise proxies |
| HTTP/2 multiplexing | Yes — multiple SSE streams over one connection | One connection per stream |
SSE is the right choice for a BFF-Pattern serving an Angular SPA when the data flow is server-initiated: notifications, background job progress, live metric dashboards, and feed updates.
Spring Boot Implementation
Spring WebFlux (via Spring-Cloud-Gateway or a standalone WebFlux controller) exposes SSE endpoints by returning Flux<ServerSentEvent<T>> with produces = MediaType.TEXT_EVENT_STREAM_VALUE.
@GetMapping(
value = "/api/notifications/stream",
produces = MediaType.TEXT_EVENT_STREAM_VALUE
)
public Flux<ServerSentEvent<NotificationDto>> streamNotifications(
@AuthenticationPrincipal Mono<OidcUser> user) {
return user.flatMapMany(u ->
notificationService.getNotificationsForUser(u.getSubject())
.map(notification -> ServerSentEvent.<NotificationDto>builder()
.id(notification.id())
.event("notification")
.data(notification)
.build())
);
}Key points:
@AuthenticationPrincipal Mono<OidcUser>— reactive security principal injection; works because the BFF session cookie carries the authenticated principalflatMapMany— converts theMono<OidcUser>to aFluxof events for that userServerSentEvent.builder()— sets the SSEid:,event:, anddata:fields- The response
Content-Type: text/event-streamkeeps the HTTP connection open
Heartbeat to Prevent Proxy Timeouts
Long-lived SSE connections are often closed by intermediate proxies after 30–60 s of silence. A periodic comment keeps the connection alive:
Flux<ServerSentEvent<NotificationDto>> heartbeat = Flux
.interval(Duration.ofSeconds(25))
.map(tick -> ServerSentEvent.<NotificationDto>builder()
.comment("heartbeat")
.build());
return Flux.merge(eventStream, heartbeat);Angular Client Implementation
Angular consumes SSE via the native browser EventSource API. The key is withCredentials: true so the session cookie is sent.
import { Injectable, OnDestroy } from '@angular/core';
import { Subject } from 'rxjs';
export interface NotificationDto {
id: string;
message: string;
type: 'INFO' | 'WARNING' | 'ERROR';
}
@Injectable({ providedIn: 'root' })
export class NotificationStreamService implements OnDestroy {
private eventSource: EventSource | null = null;
private notificationSubject = new Subject<NotificationDto>();
readonly notifications$ = this.notificationSubject.asObservable();
connect(): void {
if (this.eventSource) return;
this.eventSource = new EventSource('/api/notifications/stream', {
withCredentials: true // send session cookie
});
this.eventSource.addEventListener('notification', (event: MessageEvent) => {
const notification: NotificationDto = JSON.parse(event.data);
this.notificationSubject.next(notification);
});
this.eventSource.onerror = () => {
// EventSource reconnects automatically — log for diagnostics only
console.warn('SSE connection error, browser will reconnect');
};
}
disconnect(): void {
this.eventSource?.close();
this.eventSource = null;
}
ngOnDestroy(): void {
this.disconnect();
}
}Wrapping in RxJS
For idiomatic Angular usage, wrap EventSource in an Observable:
import { Observable } from 'rxjs';
function fromEventSource<T>(url: string, eventName: string): Observable<T> {
return new Observable(observer => {
const es = new EventSource(url, { withCredentials: true });
es.addEventListener(eventName, (e: MessageEvent) => {
observer.next(JSON.parse(e.data) as T);
});
es.onerror = (err) => observer.error(err);
return () => es.close();
});
}BFF Advantages for SSE
The BFF-Pattern is particularly well-suited for SSE:
- Authentication: the BFF already manages the session cookie — the SSE endpoint inherits the authenticated session with no extra work
- Fan-out multiplexing: the BFF can merge events from multiple upstream sources into a single SSE stream for the Angular client
- Transformation: events from disparate downstream schemas are normalised into the Angular-facing
NotificationDtobefore streaming - Security boundary: downstream services emit raw events over internal channels (Kafka, Redis Pub/Sub); the BFF decides what to expose and to which users
Relationship to Reactive Stack
SSE is a natural fit for Project-Reactor — a Flux is literally an asynchronous stream that maps 1:1 onto the SSE wire format. A blocking servlet stack (Spring MVC) can technically serve SSE but holds a thread per connection; Reactive-Programming (WebFlux) handles thousands of concurrent SSE streams with a small thread pool.
Backlinks
- BFF-Pattern — BFF exposes SSE as the real-time channel to the SPA
- BFF-For-SPA — session cookie auth works seamlessly with SSE
withCredentials - Project-Reactor —
Flux<ServerSentEvent<T>>is the reactive SSE model - Reactive-Programming — non-blocking I/O required for efficient SSE at scale
- P6-BFF-Angular-Integration — full ANG-03 coverage of SSE in the Angular BFF context