Server-Sent Events in Spring

Share

Hello, Java enthusiasts and professional developers. If you ever had to face the challenges of one-way server-client communication and are searching for a straightforward solution, you came to the right place. The technology we will go through in this short blog presents an easy and effective solution.

The basic layout of the problem is interesting. On the background (server) side we have scheduled/non-scheduled jobs that are running and completing their tasks at different time intervals. When a specific job is finished, the client side (browser) is notified. This, by itself, presents a whole number of problems to solve, and a great way to do it is by using Server-Sent Events technology.

The best way to showcase the problem and the solutions Server-Sent Events technology brings to the table is to create a simple example application. The application will be a Notification Mechanism with a number of background jobs. Client (browser) will be notified when the job is finished. Before we start creating our example, we will cover some basics of Server-Sent Events (SSE) technology so we have an easier time down the road.

Server-Sent Events technology is a part of HTML5 standard and it handles server sends messages to the client via HTTP connection. The connections are unidirectional, meaning only the server side can send a message to clients. An important thing to highlight is that the initial connection is always created by the client (browser). Thereafter, the server is ready to send messages to a specific connected client. If the connection with the server is lost, the client will try to reconnect, a feature that is supported by Server-Sent Events out of the box.

A great advantage of Server-Sent Events is that it supports all major browsers, except Microsoft Internet Explorer and Edge, but this can be easily solved using polyfill libraries. Based on my personal experience, Yaffle Event Source (https://github.com/Yaffle/EventSource) library, performed well in a serious production environment. Regarding Spring Framework and Spring boot Server-Sent Events support, it is present since version 4.2 and version 1.3 respectively.

Besides mentioned advantages, Server-Sent Events offers a lot more features on both client and server sides, but we don’t want to get things overly complicated so they will remain outside of the scope of this blog.

Now, let me show you how Server-Sent Events technology really works and how to use it in practice. As already mentioned, the example is simplified notification mechanism – Server sends job notifications to the client, the client parses those notifications and displays them.

Initial connection is created by the client (browser). In order to accomplish this, it is necessary to create an EventSource object and provide the URL of the server endpoint.

//create EventSource object that will be subscribed to 'new_notification' GET REST API
var eventSource = new EventSource('new_notification');

After the object is instantiated, the client (browser) will send a GET request with the Accept header with value text/event-stream. Taking into account that this is just a normal GET request, URL can contain parameters like any other request. Client is now ready to receive server side messages. Notice that HTTP response to client’s GET request must contain the Content-Type header with the value text/event-stream and UTF-8 encoded. Response messages are textual and they are kept in the data property of the MessageEvent object.

To process the events on the client side, an application has to have an EventSource.onmessage event handler which is called when a message event is received from a specified URL.

//Event handler that processes message events from specified URL
eventSource.onmessage = function(e) {
    var notification = JSON.parse(e.data);		
    document.getElementById("notificationDiv").innerHTML += notification.text + " at " + new Date(notification.time) + "<br/>";
};

A message received from the server doesn’t have to be a simple string. Server can send JSON strings that can be parsed by JSON.parse to a JavaScript object.
Now we need to do some cleanup. Connection is closed by simply using the EventSource close method.

//Method that closes the connection
eventSource.close();

Client (browser) will try to hold the connection open as long as possible. In the case of connection failure, the client will try to reconnect after 3 seconds. The time of reconnection can be changed by the server.

Below we can see the complete client code with initial connection and subscription using the EventSource object and server URL, event handler method that will process received server messages when they arrive, and finally the cleanup.

<!DOCTYPE html>
<html>
<head>
<title>Job Notifications</title>
<script>
var subscribe = function() {  
  var eventSource = new EventSource('new_notification');

  eventSource.onmessage = function(e) {    
    var notification = JSON.parse(e.data);               
    document.getElementById("notificationDiv").innerHTML += notification.text + " at " + new Date(notification.time) + "<br/>";
  };
}
window.onload = subscribe;
window.onbeforeunload = function() {
  eventSource.close();
 }
</script>
</head>
<body>
  <h1>Notifications: </h1>
  <div id="notificationDiv"></div>
</body>
</html>

Now it is time to explore the server/backend side of things. As an example, we will use Spring Boot application to simplify the configuration process and focus just on the Server-Sent Events technology.

To begin with we will create a POJO that holds the notification information.

public class Notification {

        public Notification(String text, Date time) {
               super();  
               this.text = text;
               this.time = time;
        } 

        ...

        public static Integer getNextJobId() {
              return ++jobId;
        } 

        private String text; 
        private Date time; 
        private static Integer jobId = 0; 
       
}

After we made our Notification POJO, it’s time to create a scheduled service that simulates the real server behavior. The server will send notification messages for job starting and finishing every 4 seconds. We can implement it using Spring’s ApplicationEventPublisher.

@Service
public class NotificationJobService {

        public final ApplicationEventPublisher eventPublisher;
        
        public NotificationJobService(ApplicationEventPublisher eventPublisher) {
                this.eventPublisher = eventPublisher;
        }

        @Scheduled(fixedRate = 4000, initialDelay = 2000)
        public void publishJobNotifications() throws InterruptedException {
                Integer jobId = Notification.getNextJobId();
                Notification nStarted = new Notification("Job No. " + jobId + " started.", new Date());
                this.eventPublisher.publishEvent(nStarted);
                Thread.sleep(2000);
                Notification nFinished = new Notification("Job No. " + jobId + " finished.", new Date());
                this.eventPublisher.publishEvent(nFinished);
        }
}

Next step is to create a REST Controller with getNewNotification GET method that will handle EventSource GET requests from the clients. To differentiate between different clients, getNewNotification METHOD should return an instance of SSEmitter. Each client connection has its own instance of SSEmitter. To manage all these emitter instances, we will store them in the list and remove them if they are completed or on timeout. Spring Boot with Tomcat 9 keeps connection open for 30 seconds, but it can be set through emitter constructor or in application.properties file (spring.mvc.async.request-timeout=60000 #connection duration 60 seconds).

Another Rest Controller method, called onNotification is annotated with @EventListener and it listens events published by eventPublisher in NotificationJobService class. In this method, we will go through the emitters list and send published notification. Here, we can see that the Notification instance will be automatically converted to JSON string. Except for the SseEmitter object, we can also send the SseEmitter builder which offers additional attributes settings.

@RestController
public class NotificationRestController {

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

        @GetMapping("/new_notification")
        public SseEmitter getNewNotification() {
                SseEmitter emitter = new SseEmitter();
                this.emitters.add(emitter);

                emitter.onCompletion(() -> this.emitters.remove(emitter));
                emitter.onTimeout(() -> {
                        emitter.complete(); 
                        this.emitters.remove(emitter);
                });

                return emitter;
        }

        @EventListener
        public void onNotification(Notification notification) {
                List<SseEmitter> deadEmitters = new ArrayList<>();
                this.emitters.forEach(emitter -> {
                        try {
                               emitter.send(notification);
                        } catch (Exception e) {
                               deadEmitters.add(emitter);
                        }
                });
                this.emitters.remove(deadEmitters);
        }

}

As we can see when we run the example code, the backend side is sending the notifications every 2 seconds and the browser is receiving them. Server-Sent Events offered us a simple way to receive server side notifications / results without the need for overly complex code.

If we inspect the example using Chrome Developer Tool (Network tab), on the first image we can see client “subscription” to server endpoint API (‘new_notification’ GET REST API) after creating an EventSource object.

Img001

The second image will show us in what form the client receives the messages from the server. As we can see SSEmitter’s send method will convert a Notification object automatically to the JSON string.

Img001

Finally, the last image displays messages in the browser after each is parsed by JSON.parse in the EventSource’s onMessage Event handler.

Img001

Working with Server-Sent Events in Spring was great satisfaction for me and I hope this article can help you start with the Server-Sent Events technology. Of course, every project carries its own challenges and specifics, but technology basics and essence should be the same.

Tags:  #java   #java_spring   #server_sent_events   #spring   #spring_boot   #spring_framework

Share

Sign in to get new blogs and news first:

Leave a Reply

Ljubica Glavaš

Software Developer @ QACube
mm

Ljubica is a senior software developer with over 12+ years of experience with cutting edge technologies in software development. Her skills range from developing desktop and mobile application to full web based platforms. She has experience working with small and dedicated teams as well as on large projects in corporate environment.

Ljubica is well versed in wide variety of technologies, but her favorite area of expertise is on Java web based technologies. Knowledge and teamwork are the things Ljubica values above everything else.

Sign in to get new blogs and news first.

Categories