Mastering Spring framework 5, Part 2: Spring WebFlux

Build reactive web applications using Spring WebFlux annotations and functional programming techniques

Spring WebFlux introduces reactive web development to the Spring ecosystem. This article will get you started with reactive systems and reactive programming with Spring. First you'll find out why reactive systems are important and how they're implemented in Spring framework 5, then you'll get a hands-on introduction to building reactive services using Spring WebFlux. We'll build our first reactive application using annotations. I'll also show you how to build a similar application using Spring's newer functional features.

Reactive systems and Spring WebFlux

The term reactive is currently popular with developers and IT managers, but I've noticed some uncertainty about what it actually means. To get clearer on what reactive systems are, it's helpful to understand the fundamental problem they're designed to solve. In this section we'll talk about reactive systems in general, and I'll introduce the Reactive Streams API for Java applications.

Blocking vs non-blocking web frameworks

In traditional web applications, when a web server receives a request from a client, it accepts that request and places it in an execution queue. A thread in the execution queue’s thread pool then receives the request, reads its input parameters, and generates a response. Along the way, if the execution thread needs to call a blocking resource--such as a database, a filesystem, or another web service--that thread executes the blocking request and awaits a response. In this paradigm the thread is effectively blocked until the external resource responds, which causes performance issues and limits scalability. To combat these issues, developers create generously sized thread pools, so that while one thread is blocked another thread can continue to process requests. Figure 1 shows the execution flow for a traditional, blocking web application.

Threaded execution model for a traditional web app. Steven Haines

Figure 1. Threaded execution model

Non-blocking web frameworks such as NodeJS and Play take a different approach. Instead of executing a blocking request and waiting for it to complete, they use non-blocking I/O. In this paradigm, an application executes a request, provides code to be executed when a response is returned, and then gives its thread back to the server. When an external resource returns a response, the provided code will be executed. Internally, non-blocking frameworks operate using an event loop. Within the loop, the application code either provides a callback or a future containing the code to execute when the asynchronous loop completes.

By nature, non-blocking frameworks are event-driven. This requires a different programming paradigm and a new approach to reasoning about how your code will be executed. Once you've wrapped your head around it, reactive programming can lead to very scalable applications.

Reactive programming

You may have heard the term reactive programming related to web development frameworks and tools, but what does it really mean? The term as we've come to know it originated from the Reactive Manifesto, which defines reactive systems as having four core traits:

  1. Reactive systems are responsive, meaning that they respond in a timely manner, in all possible circumstances. They focus on providing rapid and consistent response times, establishing reliable upper bounds so they deliver a consistent quality of service.
  2. Reactive systems are resilient, meaning that they remain responsive in the face of failure. Resilience is achieved by the techniques of replication, containment, isolation, and delegation. By isolating application components from each other, you can contain failures and protect the system as a whole.
  3. Reactive systems are elastic, meaning that they stay responsive under varying workloads. This is achieved by scaling application components elastically to meet the current demand.
  4. Reactive systems are message-driven, meaning that they rely on asynchronous message passing between components. This allows you to create loose coupling, isolation, and location transparency.

Figure 2 shows how these traits flow together in a reactive system.

Traits of a reactive system Steven Haines

Figure 2. Traits of a reactive system

Characteristics of a reactive system

Reactive systems are built by creating isolated components that communicate with one another asynchronously and can scale quickly to meet the current load. Components still fail in reactive systems, but there are defined actions to perform as a result of that failure, which keeps the system as a whole functional and responsive.

The Reactive Manifesto is abstract, but reactive applications are typically characterized by the following components or techniques:

  • Data streams: A stream is a sequence of events ordered in time, such as user interactions, REST service calls, JMS messages, and results from a database.
  • Asynchronous: Data stream events are captured asynchronously and your code defines what to do when an event is emitted, when an error occurs, and when the stream of events has completed.
  • Non-blocking: As you process events, your code should not block and perform synchronous calls; instead, it should make asynchronous calls and respond as the results of those calls are returned.
  • Back pressure: Components control the number of events and how often they are emitted. In reactive terms, your component is referred to as the subscriber and events are emitted by a publisher. This is important because the subscriber is in control of how much data it receives and thus will not overburden itself.
  • Failure messages: Instead of components throwing exceptions, failures are sent as messages to a handler function. Whereas throwing exceptions breaks the stream, defining a function to handle failures as they occur does not.

The Reactive Streams API

The new Reactive Streams API was created by engineers from Netflix, Pivotal, Lightbend, RedHat, Twitter, and Oracle, among others. Published in 2015, the Reactive Streams API is now part of Java 9. It defines four interfaces:

  • Publisher: Emits a sequence of events to subscribers.
  • Subscriber: Receives and processes events emitted by a Publisher.
  • Subscription: Defines a one-to-one relationship between a Publisher and a Subscriber.
  • Processor: Represents a processing stage consisting of both a Subscriber and a Publisher and obeys the contracts of both.

Figure 3 shows the relationship between a Publisher, Subscriber, and Subscription.

Interfaces in the Reactive Streams API Steven Haines

Figure 3. Interfaces in the Reactive Streams API

In essence, a Subscriber creates a Subscription to a Publisher and, when the Publisher has available data, it sends an event to the Subscriber with a stream of elements. Note that the Subscriber manages its back pressure inside its Subscription to the Publisher.

Now that you know a little bit about reactive systems and the Reactive Streams API, let’s turn our attention to the tools Spring uses to implement reactive systems: Spring WebFlux and the Reactor library.

Project Reactor

Project Reactor is a third-party framework based on Java's Reactive Streams Specification, which is used to build non-blocking web applications. Project Reactor provides two publishers that are heavily used in Spring WebFlux:

  • Mono: Returns 0 or 1 element.
  • Flux: Returns 0 or more elements. A Flux can be endless, meaning that it can keep emitting elements forever, or it can return a sequence of elements and then send a completion notification when it has returned all of its elements.

Monos and fluxes are conceptually similar to futures, but more powerful. When you invoke a function that returns a mono or a flux, it will return immediately. The results of the function call will be delivered to you through the mono or flux when they become available.

In Spring WebFlux, you will call reactive libraries that return monos and fluxes and your controllers will return monos and fluxes. Because these return immediately, your controllers will effectively give up their threads and allow Reactor to handle responses asynchronously. It is important to note that only by using reactive libraries can your WebFlux services stay reactive. If you use non-reactive libraries, such as JDBC calls, your code will block and wait for those calls to complete before returning.

Get started with Spring WebFlux

For our first how-to example, we'll create a simple book service that persists books to and from MongoDB in a reactive fashion.

Start by navigating to the Spring Initializr homepage, where you'll choose a Maven project with Java and select the most current release of Spring Boot (2.0.3 at time of this writing). Give your project a group name, such as "com.javaworld.webflux", and an artifact name, such as "bookservice". Expand the Switch to the full version link to show the full list of dependencies. Select the following dependencies for the example application:

  • Web -> Reactive Web: This dependency includes Spring WebFlux.
  • NoSQL -> Reactive MongoDB: This dependency includes the reactive drivers for MongoDB.
  • NoSQL -> Embedded MongoDB: This dependency allows us to run an embedded version of MongoDB, so there is no need to install a separate instance. Usually this is used for testing, but we’ll include it in our release code to avoid installing MongoDB.
  • Core -> Lombok: Using Lombok is optional as you do not need it to build a Spring WebFlux application. The benefit of using Project Lombok is that it enables you to add annotations to classes that will automatically generate getters and setters, constructors, hashCode(), equals(), and more.

When you’re finished you should see something similar to Figure 4.

Screenshot of Spring Initialzr Steven Haines

Figure 4. Screenshot of the Spring Initialzr project

Pressing Generate Project will trigger the download of a zip file containing your project source code. Unzip the downloaded file and open it in your favorite IDE. If you're using IntelliJ, choose File and then Open, and navigate to the directory where the downloaded zip file has been decompressed.

You'll find that Spring Initializr has generated two important files:

  1. A Maven pom.xml file, which includes all necessary dependencies for the application.
  2. BookserviceApplication.java, which is the Spring Boot starter class for the application.

Listing 1 shows the contents of the generated pom.xml file.

1 2 3 Page 1
Page 1 of 3