avatarYegor Voronyansky

Summary

The provided content offers a technical guide on implementing Server-Sent Events (SSE) in a Spring Boot application using Spring WebFlux and demonstrates how to enhance the data format for better consumer usability.

Abstract

The article delves into the concept of Server-Sent Events (SSE), a technology that allows servers to push updates to clients. It provides a brief history, noting that SSE has been around since 2004 and is now supported by all modern browsers. The author explains the difference between SSE and REST API's hit-and-get approach, emphasizing SSE's ability to enable clients to subscribe to a stream of data from the server. The implementation section walks through creating a Spring Boot service that simulates traffic updates every two seconds, initially using simple strings and later improving the data format by using Java records and the ServerSentEvent class for structured JSON events. The conclusion highlights the power of SSE for developing responsive applications, such as chats, where server-to-client communication is essential.

Opinions

  • The author suggests that SSE is a robust technology for real-time data streaming, contrasting it with the traditional request-response cycle of REST APIs.
  • SSE is presented as a simpler alternative to WebSockets for scenarios that only require unidirectional communication.
  • The article implies that structured data formats (like JSON) are preferred for SSE event streams due to their ease of consumption by clients.
  • By providing a step-by-step guide and code examples, the author conveys that integrating SSE into a Spring Boot application is straightforward and accessible to developers.
  • The author promotes the use of Spring WebFlux for reactive programming, which is well-suited for handling asynchronous event streams.
  • The mention of a cost-effective AI service alternative to ChatGPT Plus suggests that the author values practical and economical solutions for developers.

Spring Boot | Server-sent events (SSE)

Simple example of SSE interactions

I will show you how to use server-sent events in your Spring Boot application in this article.

Introduction

Before going into details, let’s talk about theory. The SSE is not a new fancy approach; it has been here since 2004. The first browser that started to support SSE was Opera in 2006. Now, all modern browsers support this technology. How is SSE different from the hit-and-get approach from the REST? In REST API, we hit some methods and get back some responses. In the server sents events, the consumer of REST API subscribes to events from the server.

The server-sent events are text data streams encoded in UTF-8. The data format is key-value pairs — the server-sent events to offer only uni-directional communication. Meanwhile, WebSockets offer bi-directional (full duplex) communication.

Implementation

I will use Spring Boot WebFlux in this article, but SSE can also be implemented with Spring MVC. Let’s start with a simple endpoint that will return the current traffic situation on the road every two seconds. Here is the implementation of the traffic service.

package io.vrnsky.serversenteventsdemo;

import org.springframework.stereotype.Service;

import java.util.Random;

@Service
public class TrafficService {

    private static final Random RANDOM = new Random();
    public String getTrafficStatus() {
        int random = RANDOM.nextInt(10 - 1) + 1;
        if (random < 3) {
            return "light";
        } else if (random <= 5) {
            return "moderate";
        }
        return "heavy";
    }
}

Now, let’s build an endpoint to allow consumers to subscribe to events.

package io.vrnsky.serversenteventsdemo;

import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

import java.time.Duration;
import java.time.LocalTime;

@RestController
public class EventController {

    private final TrafficService trafficService;

    public EventController(TrafficService trafficService) {
        this.trafficService = trafficService;
    }

    @GetMapping(path = "/traffic", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> streamFlux() {
        return Flux.interval(Duration.ofSeconds(2))
                .map(sequence -> String.format("%s at the time %s", trafficService.getTrafficStatus(), LocalTime.now()));
    }
}

Now, let’s run our service and check that it works correctly.

% curl http://localhost:8080/traffic    
data:moderate at the time 13:43:13.339864

data:heavy at the time 13:43:15.339383

data:heavy at the time 13:43:17.335435

data:moderate at the time 13:43:19.338967

data:heavy at the time 13:43:21.335299

data:moderate at the time 13:43:23.337038

data:light at the time 13:43:25.334921

As you may see, we got events every two seconds, as expected. However, the format of events could be more convenient for the consumer since it is not JSON or XML. Let’s fix it.

First, let’s describe our event as a Java record.

package io.vrnsky.serversenteventsdemo;

import java.time.LocalTime;

public record TrafficStatus(
        String status,
        LocalTime time
) {
}

Then, we need to adjust our TrafficService implementation to return the correct object.

package io.vrnsky.serversenteventsdemo;

import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.LocalTime;
import java.util.Random;

@Service
public class TrafficService {

    private static final Random RANDOM = new Random();
    public TrafficStatus getTrafficStatus() {
        int random = RANDOM.nextInt(10 - 1) + 1;
        if (random < 3) {
            return new TrafficStatus("light", LocalTime.now());
        } else if (random <= 5) {
            return new TrafficStatus("moderate", LocalTime.now());
        }
        return new TrafficStatus("heavy", LocalTime.now());
    }
}

Last but not least, we have to add some modifications to our endpoint.

package io.vrnsky.serversenteventsdemo;

import org.springframework.http.codec.ServerSentEvent;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;

import java.time.Duration;

@RestController
public class EventController {

    private final TrafficService trafficService;

    public EventController(TrafficService trafficService) {
        this.trafficService = trafficService;
    }

    @GetMapping(path = "/traffic")
    public Flux<ServerSentEvent<TrafficStatus>> streamFlux() {
        return Flux.interval(Duration.ofSeconds(2))
                .map(sequence -> ServerSentEvent.<TrafficStatus>builder()
                        .id(String.valueOf(sequence))
                        .event("traffic-event")
                        .data(trafficService.getTrafficStatus())
                        .build());
    }
}

After our service is up and running, we may again subscribe to events and should see something like this.

curl http://localhost:8080/traffic
id:0
event:traffic-event
data:{"status":"heavy","time":"13:50:57.245554"}

id:1
event:traffic-event
data:{"status":"light","time":"13:50:59.243631"}

id:2
event:traffic-event
data:{"status":"moderate","time":"13:51:01.243727"}

id:3
event:traffic-event
data:{"status":"moderate","time":"13:51:03.240843"}

id:4
event:traffic-event
data:{"status":"light","time":"13:51:05.242545"}

id:5
event:traffic-event
data:{"status":"heavy","time":"13:51:07.240015"}

id:6
event:traffic-event
data:{"status":"moderate","time":"13:51:09.240195"}

Conclusion

The server-sent event is a powerful technology that enables developers to build more responsive services. The approach with SSE is different from classical REST endpoints with the hit-and-get method. The SSE can be good choice for building app like chats and other types of application that require get events from server.

References

  1. Spring Boot WebFlux Docs
  2. Wikipedia
  3. HTML Standard
Java
Spring Boot
Sse
Server Sent Events
Recommended from ReadMedium