Introduction
I am going to show you an example on how to build app to send push notifications using Spring’s server sent events API. You might have seen a popular choice for sending real time data from server to client in web application is using WebSocket in our previous tutorials Messaging with STOMP over WebSockets using Spring, Angular 8 and ActiveMQ and Spring Boot WebSocket Angular 8 Gradle Example.
WebSocket opens bidirectional connections between server and client. So both server and client can send messages.
Sometimes you might face situations, where the application needs only one way communication, i.e., sending data from server to the client and for this Spring provides a simpler solution using Server Sent Events (SSE).
SSE is a technology that allows you to stream data from server to the browser (Push Notifications) within one HTTP connection in one direction.
For example, pushing stock price changes in real-time or showing progress of long-running process or real time showing of cricket or football scores on display board etc.
Browser Support
SSE are supported in most modern browsers. Only Microsoft’s IE and Edge browsers do not have a built in implementation.
But there is a way out because Server-Sent Events uses common HTTP connections and can therefore be implemented with the following libraries to support IE and Edge browsers.
- https://github.com/remy/polyfills/blob/master/EventSource.js by Remy Sharp
- https://github.com/rwldrn/jquery.eventsource by Rick Waldron
- https://github.com/amvtek/EventSource by AmvTek
- https://github.com/Yaffle/EventSource by Yaffle
Prerequisites
Java at least 8, Gradle 5.6 – 6.8.3, Maven 3.6.3, Spring Boot 2.1.8 – 2.4.4
What I am going to do
In the following example, I will create a Spring Boot application that sends the random Java’s UUID message with timestamp as SSE to the client.
Ideally you would like to display some meaningful data to the client.
So you can always modify the code as per your requirements.
The client is a simple html page that displays these values.
Spring introduced support for Server Sent Events(SSE) with version 4.2 (Spring Boot 1.3).
Project Setup
You can create either gradle or maven based spring boot project in your favorite IDE or tool. The name of the project is spring-sse-push-notifications.
The build.gradle script is given with the following content.
buildscript {
ext {
springBootVersion = '2.1.8.RELEASE' to 2.4.4
}
repositories {
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
plugins {
id 'java-library'
id 'org.springframework.boot' version "${springBootVersion}"
}
sourceCompatibility = 12
targetCompatibility = 12
repositories {
mavenCentral()
jcenter()
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web:${springBootVersion}")
}
If you are creating maven based project then you can use the following pom.xml file:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.roytuts</groupId>
<artifactId>spring-sse-push-notifications</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>12</maven.compiler.source>
<maven.compiler.target>12</maven.compiler.target>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.4</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Enabling Scheduler in Service Class
Once the project is created and build is done, create below a scheduled service that reads the greeting message every five seconds and creates an instance of the Notification class and publishes it with Spring’s event bus infrastructure.
You may change the fixed rate according to your application’s requirements.
package com.roytuts.spring.sse.push.notification.service;
import java.io.IOException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
@Service
@EnableScheduling
public class SsePushNotificationService {
final DateFormat DATE_FORMATTER = new SimpleDateFormat("dd-MM-yyyy hh:mm:ss a");
final List<SseEmitter> emitters = new CopyOnWriteArrayList<>();
public void addEmitter(final SseEmitter emitter) {
emitters.add(emitter);
}
public void removeEmitter(final SseEmitter emitter) {
emitters.remove(emitter);
}
@Async
@Scheduled(fixedRate = 5000)
public void doNotify() throws IOException {
List<SseEmitter> deadEmitters = new ArrayList<>();
emitters.forEach(emitter -> {
try {
emitter.send(SseEmitter.event()
.data(DATE_FORMATTER.format(new Date()) + " : " + UUID.randomUUID().toString()));
} catch (Exception e) {
deadEmitters.add(emitter);
}
});
emitters.removeAll(deadEmitters);
}
}
In the above code you can see that I am pushing data every 5 secs to the client.
Related Posts:
- Spring Asynchronous Execution using @Async
- Messaging with STOMP over WebSockets using Spring, Angular 8 and ActiveMQ
- Spring Boot WebSocket Angular 8 Gradle Example
Spring Rest Controller
Next I will create a REST Controller class that handles the EventSource
GET request from the client.
The GET handler needs to return an instance of the class SseEmitter
.
Each client connection is represented with its own instance of SseEmitter
.
Spring does not give you tools to manage these SseEmitter
instances. In this application I store the emitters in a simple list(emitters) and add handlers to the emitter’s completion and timeout event to remove them from the list.
package com.roytuts.spring.sse.push.notification.controller;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import com.roytuts.spring.sse.push.notification.service.SsePushNotificationService;
@RestController
public class SsePushNotificationRestController {
@Autowired
SsePushNotificationService service;
final List<SseEmitter> emitters = new CopyOnWriteArrayList<>();
@GetMapping("/notification")
public ResponseEntity<SseEmitter> doNotify() throws InterruptedException, IOException {
final SseEmitter emitter = new SseEmitter();
service.addEmitter(emitter);
service.doNotify();
emitter.onCompletion(() -> service.removeEmitter(emitter));
emitter.onTimeout(() -> service.removeEmitter(emitter));
return new ResponseEntity<>(emitter, HttpStatus.OK);
}
}
Configure application.properties
By default, Spring Boot with the embedded Tomcat server keeps the SSE HTTP connection open for 60 seconds. An application can change that with an entry to the application.properties
file
spring.mvc.async.request-timeout=-1 #-1 means infinity
Spring Boot Main Class
Create below main class to start up the application into embedded Tomcat server.
package com.roytuts.spring.sse.push.notification;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(scanBasePackages = "com.roytuts.spring.sse.push.notification")
public class SpringSsePushNotificationApp {
public static void main(String[] args) {
SpringApplication.run(SpringSsePushNotificationApp.class, args);
}
}
Client Part – UI
The client opens the SSE connection with
const eventSource = new EventSource('http://localhost:9999/notification');
and registers a message listener that parses the JSON and sets the innerHTML of three dom elements to display the received data.
The whole HTML file with content is given below:
<!DOCTYPE html>
<html>
<head>
<title>Spring SSE Push Notifications</title>
<script>
function initialize() {
const eventSource = new EventSource('http://localhost:8080/notification');
eventSource.onmessage = e => {
const msg = e.data;
document.getElementById("greet").innerHTML = msg;
};
eventSource.onopen = e => console.log('open');
eventSource.onerror = e => {
if (e.readyState == EventSource.CLOSED) {
console.log('close');
}
else {
console.log(e);
}
};
eventSource.addEventListener('second', function(e) {
console.log('second', e.data);
}, false);
}
window.onload = initialize;
</script>
</head>
<body>
<div id="greet"></div>
</body>
</html>
Testing the Application
Now while you run the client file in any modern browsers like Chrome, Firefox etc. then you may face below problem
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource spring boot html
Adding CORS
To resolve this issue add @CrossOrigins(origins = "*")
to the REST controller class to allow from all host. If you want to restrict to a particular host, for example, http://www.example.com then you need to put as @CrossOrigins(origins = "http://www.example.com")
.
The revised REST controller class is given as:
package com.roytuts.spring.sse.push.notification.controller;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import com.roytuts.spring.sse.push.notification.service.SsePushNotificationService;
@RestController
@CrossOrigin(origins = "*")
public class SsePushNotificationRestController {
@Autowired
SsePushNotificationService service;
final List<SseEmitter> emitters = new CopyOnWriteArrayList<>();
@GetMapping("/notification")
public ResponseEntity<SseEmitter> doNotify() throws InterruptedException, IOException {
final SseEmitter emitter = new SseEmitter();
service.addEmitter(emitter);
service.doNotify();
emitter.onCompletion(() -> service.removeEmitter(emitter));
emitter.onTimeout(() -> service.removeEmitter(emitter));
return new ResponseEntity<>(emitter, HttpStatus.OK);
}
}
Refresh the client file in browser, you will get uninterrupted message being displayed and the following random content will be changing on each 5 seconds.

That’s all. Hope you got an idea on Server Sent Events with Spring example.
It s realy helpful. Thank you
Thankyou for the awesome post. Helped.