Reactive Programming, Project Reactor, Spring WebFlux, Oh my!!

Katie Levy
Intuit Engineering
Published in
6 min readMay 29, 2020

Reactive programming solves two important things: performance and memory. It will save you money and, more importantly, create a faster experience for your users. In terms of backend services, a service written with reactive programming will be able to withstand a much higher load since the server will be non-blocking with its asynchronous nature. The service can handle more requests with the same amount of hardware (under the condition that our work is I/O bound and not CPU bound).

So what is reactive programming? It’s programming with asynchronous data streams and propagation of change. Or in other words,

“The reactive design pattern is an event-based architecture for asynchronous handling of a large volume of concurrent service requests coming from single or multiple service handlers” — Baeldung

If you are familiar with Java 8 Streams, the key difference is that in a reactive approach, events are pushed to subscribers, whereas events are pulled to Java 8 Streams.

Still sounds confusing? Let’s break it down even more! Imagine you’re working the register at your favorite burger place (mine’s In-N-Out!).

You have to:

  1. Take a customer’s order
  2. Tell the cook the order
  3. Wait for the order to be completed
  4. Give the order to the customer

Imagine if you did all four steps for an order before moving on to the next customer;

  1. You took the customer’s order
  2. Told the cook the order
  3. Waited for the food to be cooked
  4. Gave it to the customer
  5. And after all of that, then took the next person in line’s order.

You’d have a line out the door and customers waiting for hours — not in a good way!

Let’s optimize this:

  1. Take a customer’s order
  2. Tell the cook
  3. Instead of waiting for it to be cooked, take the next person’s order and listen for (subscribe to) the cook to tell you the first customer’s food is done
  4. Once the food is done, you give the food to the first customer

This concurrent operation speeds up your process with the same amount of employees. We can handle more requests with the same amount of employees by using a publisher and subscriber model. Every time there is a new customer, you’re handing off the order to the cook and then subscribing to the finished burger which the cook publishes. You get more work done during the time you would normally be waiting for the cook to cook the order. This pattern allows other objects to be notified of an object’s status change, ultimately optimizing the use of resources.

We are in the mode of reacting to notifications as operations complete or data becomes available.

Now how do we bring this into our code?

You can think of yourself (the cashier) as a thread operating on an incoming network request. You make an outgoing network request to the cook (a different service) and then wait for the response. This operation is usually blocking, but by using a reactive programming library, you can transform your code into a non-blocking mechanism. There are a few different reactive programming libraries including, Project Reactor, RxJava, Reactive Streams, and the newest, Kotlin Flow.

All of these libraries have a concept of an asynchronous sequence of 0..N items and an asynchronous sequence of 0..1 items; they are just called by different names.

For example, a Flux in Project Reactor is 0..N items:

And a Mono is 0..1 items:

Now let’s see how they are represented in some of the popular Reactive Programming Libraries:

There are many pros and cons to deciding which reactive programming library to use, but at the end of the day, they all make your application more optimized. To take a more in-depth look at Kotlin Flow vs. Project Reactor, take a look at What the Flow? This article goes into detail about an important issue, backpressure, which Kotlin Flow solves eloquently through using Kotlin coroutines.

Backpressure: a mechanism to ensure producers don’t overwhelm consumers. If a producer is sending more than the consumer can keep up with, there needs to be a mechanism to tell the producer to slow down or stop completely until the consumer is ready for more work.

With any of these reactive programming libraries, we can have a more non-blocking application, including outgoing requests. We can use Spring’s non-blocking client, WebClient, rather than the usual RestTemplate and use our handy reactive data models Flow, Flux, Mono, Flowable or Maybe to handle the reactiveness of outgoing requests.

Reactive outgoing requests and reactive code both sound great, but it doesn’t stop here. We have a reactive application, but our service is not FULLY reactive. This is where Spring WebFlux comes into the picture.

Spring WebFlux supports reactive HTTP and WebSocket clients as well as reactive server web applications, including REST, HTML browser, and WebSocket style interactions. WebFlux allows for more scalability, a stack immune to latency (useful for micro services-oriented architecture), and better stream processing capabilities. You can easily have an application with the Spring MVC model for one controller and the WebFlux model for another controller.

We can compare and contrast the usual Spring MVC model with the Spring WebFlux model.

Controller Level

You can easily convert a controller from Spring MVC to WebFlux as the annotations, like @Controller, all are the same, making migration an easy process. However, there is another option for WebFlux; functional endpoints. This is a more functional manner of defining APIs through a router and handler mechanism. Engineers familiar with the functional programming style tend to lean towards the functional endpoints, but at the end of the day, it behaves the same as the controller model — just a style preference. You can see an example of functional endpoints here.

Clients (Outgoing Requests)

Both can have reactive clients, as we talked about earlier with WebClient.

Blocking Dependencies

Applications with blocking dependencies, like JDBC and JPA, do not benefit as much by using WebFlux. It is possible, but most likely not worth the additional complexity.

Servers

Both can run on Tomcat, Jetty, and Undertow servers. WebFlux, however, can also run on the entirely reactive server of Netty, which is the recommended server for WebFlux applications.

There is a time and place to use the Spring MVC model, but in the burger joint scenario, a Spring WebFlux model would offer key gains to our application. At Intuit, we’ve been working on migrating some of our backend services to this reactive model to deliver better quality for our customers.

I won’t pretend that it’s easy to pick up reactive programming — there is a bit of a learning curve — but I want to emphasize the benefits as it can revolutionize an application: performance improvements, cost reduction, and tools to implement complex threading operations. In other words, the time spent learning reactive programming is well worth it. There are many resources/learning materials available online, some of them listed below. Hopefully, I’ve given you a basic understanding of how awesome reactive programming is so you can start your journey!

--

--