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 a different time intervals. When a specific job is finished, 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 SSE 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 easier time down the road.

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

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

Besides mentioned advantages, SSE 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, example is simplified notification mechanism – Server sends jobs notifications to client, 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 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 object is instantiated, client (browser) will send 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 data property of MessageEvent object.

To process the events on the client side, an application has to have EventSource.onmessage event handler which is called when message event is received from 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/>";
};

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 JavaScript object.
Now we need to do some cleanup. Connection is closed by simply using 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, 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 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 configuration process and focus just on Server-Sent Events technology.

To begin with we will create POJO that holds 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 scheduled service that simulates real server behavior. 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 REST Controller with getNewNotification GET method that will handle EventSource GET request from the client. 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 emitters list and send published notification. Here, we can see that Notification instance will be automatically converted to JSON string. Except SseEmitter object, we can also send SseEmitter builder which offer 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, backend side is sending the notifications every 2 seconds and the browser is receiving them. SSE 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 EventSource object.

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

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

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

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