Spring Boot 3 - server-sent events example using Spring MVC
In this article, we would like to show how to use server-sent events with Spring MVC under Spring Boot 3.
In the example project we used HTTP/2 protocol for better performance.
Warning from MDN:
When not used over HTTP/2, SSE suffers from a limitation to the maximum number of open connections, which can be specially painful when opening various tabs as the limit is per browser and set to a very low number (6). The issue has been marked as "Won't fix" in Chrome and Firefox. This limit is per browser + domain, so that means that you can open 6 SSE connections across all of the tabs to
www.example1.com
and another 6 SSE connections towww.example2.com.
(from Stackoverflow). When using HTTP/2, the maximum number of simultaneous HTTP streams is negotiated between the server and the client (defaults to 100).Source: https://developer.mozilla.org/en-US/docs/Web/API/EventSource

Project was generated using this configuration (by using GENERATE button it is possible to download the project).
To generate src/main/resources/cert.pem
and src/main/resources/key.pem
files use the following command:
xxxxxxxxxx
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes -subj '/CN=localhost'
Hint: to know how to generate certificate files under Windows read this article.
src/main/resources/application.properties
file:
xxxxxxxxxx
server.port=8443
server.ssl.enabled=true
server.ssl.certificate=classpath:cert.pem
server.ssl.certificate-private-key=classpath:key.pem
server.http2.enabled=true
src/main/resources/static/index.html
file:
xxxxxxxxxx
<html>
<body>
<script>
const source = new EventSource('/events'); // OR: https://localhost:8443/events
source.onopen = (e) => console.log('Event source stream open!');
source.onerror = (e) => console.error('Event source stream error!');
// Handling specific events:
source.addEventListener('my-message', (e) => console.log('Message: ' + e.data));
// Add here more source.addEventListener(...) calls for different events ...
</script>
</body>
</html>
Note: event source can be closed by
source.close()
method call.
src/main/java/com/example/demo/DemoApplication.java
file:
xxxxxxxxxx
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
src/main/java/com/example/demo/MainController.java
file:
xxxxxxxxxx
package com.example.demo;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter.SseEventBuilder;
public class MainController {
private ExecutorService executor = Executors.newCachedThreadPool();
(
value = "/",
method = RequestMethod.GET
)
public String index() {
return "index.html";
}
(
value = "/events",
method = RequestMethod.GET
)
public SseEmitter events() {
SseEmitter emitter = new SseEmitter(); // 1 second timeout
emitter.onCompletion(() -> System.out.println("Event source stream closed!"));
emitter.onTimeout(() -> System.out.println("Event source stream timeout!"));
emitter.onError((Throwable ex) -> System.out.println("Event source stream error!"));
this.executor.execute(() -> {
int counter = 0;
try {
while (true) {
String id = Long.toString(++counter);
SseEventBuilder event = SseEmitter.event()
.id(id)
.name("my-message")
.data("Some text message here ...");
emitter.send(event);
Thread.sleep(1000);
}
} catch (Exception ex) {
emitter.completeWithError(ex);
}
});
return emitter;
}
}
pom.xml
file:
xxxxxxxxxx
<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>server-sent-events</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>server-sent-events</name>
<description>Server-sent events example using Spring Boot project.</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
To download dependencies and compile project use the following command:
xxxxxxxxxx
mvn clean compile
To run server use the following command:
xxxxxxxxxx
mvn exec:java -Dexec.mainClass=com.example.demo.DemoApplication
Then, to see the final effect, open the link in the web browser: https://localhost:8443
Example preview:
