Server-Sent Events (SSE from now on) it’s a technology which provides real-time communication between web clients and servers. They’re similar to WebSockets, but SSE are suitable for scenarios where the server needs to send data to clients unidirectionally while websockets are for bidirectional communication.
Here at Ensolvers we've found out the usefulness of this technology for providing server to client real-time updates in our web applications, avoiding constant polling from both sides and in a simpler way in comparison to Websockets.
What motivated us to start using this technology was a specific requirement from one of our customers regarding a live chat functionality. In this context, we have already implemented that functionality with AWS Chime by using the Websocket connection capability already provided out of the box by AWS SDK. The challenge started when we had to start to analyze the messages interchanged in the chats to categorize and prioritize them after sending, a task that is done asynchronously on the server using AI.
To perform this categorization correctly and also inform the client of the necessary change to be displayed in the UI we could not use the AWS chime Websockets we already had as these cannot be used arbitrarily but only for purely sending messages with small metadata. Since AWS SDK has control of that Websockets communication, it was not possible to even perform a "message decoration" to add the required analysis. Finally, since that processing is done asynchronously, we don't want to delay message interchange just because of this analysis. So, that is where SSEs became a solution.
SSE allows the server to push real-time updates or events to the connected clients over a long-lived HTTP connection. We’re going to dive a basic overview of how SSE works
The client initiates a request to the server using the EventSource API in JavaScript. The server responds to this request with an HTTP connection sending events in text/event-stream format while it remains open. These incoming messages from the server are delivered to the code in the form of events. If there is an event field in the incoming message, the triggered event is the same as the event field value. If no event field is present, then a generic message event is fired, until the connection is closed by calling EventSource.close().
This object requires the specification of the endpoint to which it will be subscribed, along with a listener eventType to which it will be waiting.
On the server side, we can use Spring SSE support to simplify returning a SseEmitter which will allow us to send events from the server to the client. We do use a param to represent what user made the connection (personalExternalId) and the event type needs to be notified (eventToSubscribe).
Now, let's dig into the EmitterService class and registerClientToSet method, which is core in this implementation
The server sends events to the client as plain text in a specific format. Each event is preceded by the event field, and the data associated with the event is specified using the data field. For Java, we used SSEEmitter to keep the connection with the client:
Also, we’ve defined a SseClient to store the data of the subscribed clients, the SseEmitter related to the client and a List of subscribed event types to know, when broadcasting, if should receive or not a notification:
For the subscribed users, we store the clients in a ConcurrentMap<String, SseClient> to be allowed to resend messages to them every time we need to. This might present a big issue when working on distributed environments, and will be treated as future work.
If the connection is interrupted (e.g., due to network issues, or Timeouts), the client automatically attempts to reconnect. This allows for a resilient communication channel. Even though the connection is a long-living one, we have had to implement a heartbeat - a "dummy" message that just ensures that the connection is kept alive. The reason for this necessity lies in the fact that the connection gets closed from the client side (browser) after one minute without communication.
After all this set up, the server can push updates or events to the connected clients at any time. For instance, in the code stub below, we show how we can broadcast a message to all clients.
This enables real-time communication without the need for the client to repeatedly poll the server.
On the client side, JavaScript can listen for specific events and handle them accordingly.
After this step, we have gone through our implementation from end to end. Using this same strategy, we can send different types of messages via SSE.
Server-Sent Events (SSE) offer a compelling solution for real-time communication between web clients and servers, particularly in scenarios where unidirectional data flow is sufficient. In contrast to WebSockets, SSE provides a simpler mechanism that eliminates the need for constant polling and reduces infrastructure costs. In this article we've shown how SSE can be easily implemented using standard JS APIs on the client-side and some utils classes provided by Spring on the server-side. As was mentioned before, this solution does not scale for distributed environments - for instance, when we have a cluster of nodes running in the backend - but we will tackle that problem in a subsequent article.