Spring Boot Server-Sent Events Tutorial Using WebFlux
Welcome, in this tutorial, we will explain the server-sent events in a spring boot application using WebFlux. In this approach, we will use the Reactive SSE where the service returns a Flux
of transactions.
1. Introduction
Before going further in this Spring Boot Server-Sent Events Tutorial using WebFlux, we will look at the common terminology such as introduction to Spring Boot, Lombok, and Server-Sent Events (SSE).
1.1 Spring Boot
- Spring boot is a module that provides rapid application development feature to the spring framework including auto-configuration, standalone-code, and production-ready code
- It creates applications that are packaged as jar and are directly started using embedded servlet container (such as Tomcat, Jetty or, Undertow). Thus, no need to deploy the war files
- It simplifies the maven configuration by providing the starter template and helps to resolve the dependency conflicts. It automatically identifies the required dependencies and imports them in the application
- It helps in removing the boilerplate code, extra annotations, and XML configurations
- It provides a powerful batch processing and manages the rest endpoints
- It provides an efficient JPA-starter library to effectively connect the application with the relational databases
- It offers a Microservice architecture and cloud configuration that manages all the application related configuration properties in a centralized manner
1.2 Lombok
- Lombok is nothing but a small library which reduces the amount of boilerplate Java code from the project
- Automatically generates the getters and setters for the object by using the Lombok annotations
- Hooks in via the Annotation processor API
- Raw source code is passed to Lombok for code generation before the Java Compiler continues. Thus, produces properly compiled Java code in conjunction with the Java Compiler
- Under the
target/classes
folder you can view the compiled class files - Can be used with Maven, Gradle IDE, etc.
1.2.1 Lombok features
Feature | Details |
---|---|
val | Local variables are declared as final |
var | Mutable local variables |
@Slf4J | Creates an SLF4J logger |
@Cleanup | Will call close() on the resource in the finally block |
@Getter | Creates getter methods for all properties |
@Setter | Creates setter for all non-final properties |
@EqualsAndHashCode |
|
@ToString |
|
@NoArgsConstructor |
|
@RequiredArgsContructor |
|
@AllArgsConstructor |
|
@Data |
|
@Builder |
|
@Value |
|
1.3 Server-Sent Events (SSE)
Server-Sent Events (SSE) is a web technology where a browser receives updates from a server via the HTTP connection and offers a better approach then polling as polling causes a lot of HTTP overhead. It is unidirectional (i.e. server to browser) and the events are sent over the traditional HTTP, hence no special implementation is required on the server. It is used to –
- Replace long polling
- Enable applications that use the server to browser data communication
Spring Webflux is an async and non-blocking reactive web stack that enables the handles of a massive number of concurrent connections. It supports reactive streams backpressure and runs on servers such as Netty. Helps to enable vertical scaling of the service to handle a greater load.
2. Spring Boot Server-Sent Events Tutorial Using WebFlux
Here is a systematic guide for implementing this tutorial but before going any further I’m assuming that you are aware of the Spring boot.
2.1 Application Pre-requisite
To start with this tutorial, we are hoping that you at present have the Lombok plugin installed in the IDE of their favorite choice. If someone needs to go through the Lombok installation on IntelliJ IDE, please watch this video. For installation on Eclipse IDE, please watch this video.
2.2 Tools Used and Project Structure
We are using Eclipse Kepler SR2, JDK 8, and Maven. In case you’re confused about where you should create the corresponding files or folder, let us review the project structure of the spring boot application.
Let us start building the application!
3. Creating a Spring Boot application
Below are the steps involved in developing the application.
3.1 Maven Dependency
Here, we specify the dependency for the Spring Boot, WebFlux, Thymeleaf, Faker, and Lombok. Maven will automatically resolve the other dependencies. The updated file will have the following code.
pom.xml
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | 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 > < groupId >com.springboot.server-events</ groupId > < artifactId >SpringbootServerEvents</ artifactId > < version >0.0.1-SNAPSHOT</ version > <!-- application name --> < name >Springboot Server Events tutorial</ name > < description >A tutorial on springboot and server events</ description > < properties > < java.version >1.8</ java.version > </ properties > <!-- spring boot starter parent dependency --> < parent > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-parent</ artifactId > < version >2.3.4.RELEASE</ version > </ parent > < dependencies > <!-- spring boot webflux dependency --> < dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-webflux</ artifactId > </ dependency > <!-- spring boot thymeleaf dependency --> < dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-thymeleaf</ artifactId > </ dependency > <!-- faker dependency --> < dependency > < groupId >com.github.javafaker</ groupId > < artifactId >javafaker</ artifactId > < version >1.0.2</ version > </ dependency > <!-- lombok dependency --> < dependency > < groupId >org.projectlombok</ groupId > < artifactId >lombok</ artifactId > < scope >provided</ scope > </ dependency > </ dependencies > < build > <!-- final jar name --> < finalName >SpringbootServerEvents</ finalName > <!-- to make the application as fat jar so that spring boot libraries are included --> < plugins > < plugin > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-maven-plugin</ artifactId > </ plugin > </ plugins > </ build > </ project > |
3.2 Application Properties
Create a new properties file at the location: SpringbootServerEvents/src/main/resources/
and add the following code to it.
application.properties
1 2 | server.port=10093 spring.application.name=springboot-and-server-events |
3.3 Java Classes
Let us write all the java classes involved in this application.
3.3.1 Implementation/Main class
Add the following code to the main class to bootstrap the application from the main method. Always remember, the entry point of the spring boot application is the class containing @SpringBootApplication
annotation and the static main method.
SpringbootServerEvents.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 | package com.springboot.serverevents; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; // Lombok annotation // Causes Lombok to generate a logger field. @Slf4j // Spring framework annotation // Main implementation class which serves two purposes in a spring boot application: Configuration and bootstrapping. @SpringBootApplication public class SpringbootServerEvents { // Main program to start up the spring boot application. public static void main(String[] args) { SpringApplication.run(SpringbootServerEvents. class , args); log.info( "Spring-boot server events application started successfully." ); } } |
3.3.2 Model class
Add the following code to the Book model class.
GameOfThrone.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | package com.springboot.serverevents.model; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.stereotype.Component; // Lombok annotations // Causes lombok to generate toString(), equals(), hashCode(), getter() & setter(), and Required arguments constructor in one go. @Data // Causes Lombok to implement the Builder design pattern for the Pojo class. // Usage can be seen in GameOfThroneReactiveRepository.java -> get() method. @Builder // Causes Lombok to generate a constructor with no parameters. @NoArgsConstructor // Causes Lombok to generate a constructor with 1 parameter for each field in your class. @AllArgsConstructor // Spring framework annotation @Component public class GameOfThrone { String house; String character; String dragon; String city; } |
3.3.3 Configuration class
Add the following code to the bean class that will return the bean object for the faker
object. The usage of this object can be seen in the GameOfThroneReactiveRepository.java
class.
BeanConfiguration.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 | package com.springboot.serverevents.config; import com.github.javafaker.Faker; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.Locale; @Configuration public class BeanConfiguration { @Bean public Faker faker() { return new Faker( new Locale( "en-US" )); } } |
3.3.4 Data-Access-Object class
Add the following code to the Dao class that returns the Flux
events. Currently, we are returning the dummy events for the simplest implementation and understanding.
GameOfThroneReactiveRepository.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | package com.springboot.serverevents.repo; import com.github.javafaker.Faker; import com.springboot.serverevents.model.GameOfThrone; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; import reactor.core.publisher.Flux; import java.time.Duration; import java.util.Collections; import java.util.List; @Repository public class GameOfThroneReactiveRepository { @Autowired private Faker faker; public Flux<GameOfThrone> findAll() { // Simulate the data streaming every 2 seconds. return Flux.interval(Duration.ofSeconds( 2 )) .onBackpressureDrop() .map(interval -> get()) .flatMapIterable(v -> v); } private List<GameOfThrone> get() { final GameOfThrone gameOfThrone = GameOfThrone.builder() .house(faker.gameOfThrones().house()) .character(faker.gameOfThrones().character()) .dragon(faker.gameOfThrones().dragon()) .city(faker.gameOfThrones().city()).build(); return Collections.singletonList(gameOfThrone); } } |
3.3.5 Service class
Add the following code to the service class where we will call the methods of the Dao class to fetch the data.
GameOfThroneService.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | package com.springboot.serverevents.service; import com.springboot.serverevents.model.GameOfThrone; import com.springboot.serverevents.repo.GameOfThroneReactiveRepository; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import reactor.core.publisher.Flux; // Lombok annotation // Causes Lombok to generate a logger field. @Slf4j // Spring framework annotation @Service public class GameOfThroneService { @Autowired GameOfThroneReactiveRepository gameOfThroneReactiveRepository; public Flux<GameOfThrone> getAllThronesFlux() { log.info( "Fetching the game of throne data from the persistent storage and sending back via the flux mechanism." ); return gameOfThroneReactiveRepository.findAll(); } } |
3.3.6 Index Controller class
Add the following code to the controller class designed to handle the incoming requests. The class is annotated with the @Controller
annotation were the HTTP GET
method would return the index
page of the application.
IndexController.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 | package com.springboot.serverevents.controller; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; // Lombok annotation // Causes Lombok to generate a logger field. @Slf4j // Spring framework annotation @Controller public class IndexController { private static final String INDEX_PAGE = "index" ; // URL - http://localhost:10093/ @GetMapping (value = "/" ) public String index() { log.info( "Returning the application's welcome page to the user." ); return INDEX_PAGE; } } |
3.3.7 Rest Controller class
Add the following code to the controller class designed to handle the incoming requests. The class is annotated with the @RestController
annotation were the HTTP GET
method would return the flux
events.
GameOfThroneRestController.java
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | package com.springboot.serverevents.controller; import com.springboot.serverevents.model.GameOfThrone; import com.springboot.serverevents.service.GameOfThroneService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Flux; // Lombok annotation // Causes Lombok to generate a logger field. @Slf4j // Spring framework annotation @RestController @RequestMapping (value = "/api" ) public class GameOfThroneRestController { @Autowired GameOfThroneService gameOfThroneService; @GetMapping (path = "/gameOfThroneStream" , produces = MediaType.TEXT_EVENT_STREAM_VALUE) public Flux<GameOfThrone> gameOfThroneStream() { log.info( "Returning the game of throne streaming events via flux mechanism." ); return gameOfThroneService.getAllThronesFlux(); } } |
4. Thymeleaf Changes
We will create a simple HTML page that will display the reactive events on the browser. Create a new HTML file at the location: SpringbootServerEvents/src/main/resources/templates/
and add the following code to it.
index.html
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | <! DOCTYPE html> < html lang = "en" > < head > < meta charset = "UTF-8" > < title >Index</ title > < link href = "https://examples.javacodegeeks.com/wp-content/litespeed/localres/aHR0cHM6Ly9tYXhjZG4uYm9vdHN0cmFwY2RuLmNvbS8=bootstrap/3.3.7/css/bootstrap.min.css" rel = "stylesheet" > < style > .bold-font { font-weight: bold; } th, td { text-align: center; } </ style > </ head > < body > < div class = "container" > < h2 class = "text-muted" >Spring Boot and Server Events Tutorial Using Webflux</ h2 > < table class = "table table-striped" id = "gameOfThrones" > < thead > < tr > < th class = "bold-font" >House</ th > < th class = "bold-font" >Character</ th > < th class = "bold-font" >Dragon</ th > < th class = "bold-font" >City</ th > </ tr > </ thead > < tbody > < tr data-th-each = "gameOfThrone : ${gameOfThrones}" > < td >[[${gameOfThrone.house}]]</ td > < td >[[${gameOfThrone.character}]]</ td > < td >[[${gameOfThrone.dragon}]]</ td > < td >[[${gameOfThrone.city}]]</ td > </ tr > </ tbody > </ table > </ div > < script data-th-src = "@{/js/main.js}" ></ script > </ body > </ html > |
5. JavaScript Changes
To listen to the message events
we will use the JavaScript EventSource
object and update the streaming data into the HTML table. Create a new JS file at the location: SpringbootServerEvents/src/main/resources/static/js/
and add the following code to it.
main.js
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | function LoadGameOfThronesData() { this .source = null ; this .start = function () { let gameOfThronesTable = document.getElementById( "gameOfThrones" ); this .source = new EventSource( "/api/gameOfThroneStream" ); this .source.addEventListener( "message" , function (event) { // These events are JSON, so parsing. let gameOfThrone = JSON.parse(event.data); let rowElement = gameOfThronesTable.getElementsByTagName( "tbody" )[0].insertRow(0); let cell0 = rowElement.insertCell(0); let cell1 = rowElement.insertCell(1); let cell2 = rowElement.insertCell(2); let cell3 = rowElement.insertCell(3); cell0.innerHTML = gameOfThrone.house; cell1.innerHTML = gameOfThrone.character; cell2.innerHTML = gameOfThrone.dragon; cell3.innerHTML = gameOfThrone.city; }); this .source.onerror = function () { this .close(); }; }; this .stop = function () { this .source.close(); }; } gameOfThrones = new LoadGameOfThronesData(); /* * Register callbacks for starting and stopping the SSE controller. */ window.onload = function () { gameOfThrones.start(); }; window.onbeforeunload = function () { gameOfThrones.stop(); } |
6. Run the Application
To execute the application, right-click on the SpringbootServerEvents.java
class, Run As -> Java Application
.
7. Project Demo
Open the browser of your choice and hit the following URL. The stream of data having the game of throne characters data will be displayed every 2 seconds.
That is all for this tutorial and I hope the article served you whatever you were looking for. Happy Learning and do not forget to share!
8. Server-Sent Events using Webflux – Summary
In this Spring Boot Server-Sent Events tutorial using WebFlux, you learned:
- Spring Boot, Lombok and it features, and SSE (Server-Sent Events)
- Flux implementation in Spring Boot and displaying the streaming data on the browser
You can download the sample application as an Eclipse project in the Downloads section.
9. Download the Eclipse Project
This was an example of Server-Sent Events (SSE) in Spring Boot using WebFlux.
You can download the full source code of this example here: Spring Boot Server-Sent Events Tutorial Using WebFlux