A reactive Java landscape

A reactive Java landscape

In tons of ways your shiny new Java application will benefit from taking a reactive approach. Maybe it’s because the app is like a hole in the dam to your data lake and you don’t want to overflow any clients that access it. Or maybe you expect a ton of users and they should all be notified about each other in real time. Or just maybe you have a colleague who threw you into this reactive programming adventure and now you wonder why you’re using Project Reactor instead of the Java Flow API. Whatever the case may be, it’s a good idea to take a birds-eye view and admire the landscape of reactive programming concepts, API’s and implementations. Reactive programming in Java has had time to mature and in this blog post I am going present a couple of high-level concepts, the Java native Flow API and two contending (or cooperating?) frameworks.

The foundation

In a nutshell the reactive programming paradigm aims to process asynchronous data streams in a responsible manner. This responsible manner of setting up reactive systems is summarized in the reactive manifesto, which without going into too much detail sets up some constraints on what a reactive system is. In reactive programming we try to adhere to these criteria by using message-based communication between different parts of our program.

You are probably familiar with messaging in a publish-subscribe pattern where a publisher sends messages to its subscribers who accept them in some sort of buffer and process them at their own pace. In this model there has to be an obvious balance between subscribers and publishers. Too many messages for the subscriber and its buffer will only keep growing with possibly catastrophic results. Too few messages and our system is constantly idling waiting for messages and is wasting potential processing capacity. This is where the reactive concept of backpressure comes in, a subscriber exerts this pressure indicating to the publisher its processing capabilities. Combine this with some clever scaling and the system becomes reactive, both the subscriber and publisher are aware of each other’s operational capacity and the system reacts accordingly.

One last major concept to understand a fully reactive Flow for your messages is that all operations in a flow should be non-blocking. This means that your program should not halt its complete operation waiting for the result of one instruction, instead we’d like to pick up any work that is available in the system. In a general sense we do this by getting a bit meta and describing the relations between data and instructions in a more functional manner instead of listing instructions sequentially (or imperatively) like you might be used to as a Java programmer.

The specification

The reactive streams initiative has provided us a universal language for reactive programming concepts to define the flow of messages. The interfaces first defined in their own package, org.reactivestreams, have since JDK 9 moved to the interfaces in java.util.concurrent.Flow:

  • Flow.Publisher<T>
  • Flow.Subscriber<T>
  • Flow.Processor<T, R> extends Flow.Subscriber<T>, Flow.Publisher<R>
  • Flow.Subscription

These interfaces naturally don’t put many restrictions on your implementation, meaning you are free to implement them as blocking and backpressureless as you can or find appropriate for your application.

The implementations

In the current Java climate we find two major implementations of the reactive streams specification: RxJava and Reactor. Both frameworks have an extensive library of utility functions for creating, transforming, filtering and combining Publishers. Even though each framework implements Publishers in their own way, once you get past the differences in naming the operations are generally the same. For example: RxJava has the Flowable<T> and Maybe<T> classes, where Reactor uses Flux<T> and Mono<T>. By the way, this is also immediately an example where the implementations decided to depart from the specification. The Maybe and Mono classes are special cases of Publishers that only emit at most one element (or fail). While they are not explicitly a part of the reactive streams specification, they make sense in everyday use cases like retrieving a uniquely identified element from a repository.

Let’s work out a little example to illustrate the similarity of the two frameworks. Imagine we’re setting up a system to monitor temperatures, and we start by making a class to make sensor readings available. This is what it might look like using the Reactor framework:

import java.time.Duration; 
import reactor.core.publisher.Flux; 

public final class ReactorSensor { 

    public static final Flux<Float> TEMPERATURES = 
            Flux.interval(Duration.ofSeconds(1)) 
                    .map(ignored -> getCurrentTemperature()) 
                    .share(); 

    private static float getCurrentTemperature () { 
        //Left blank 
    } 
}

Here I defined a Publisher that emits the current temperature every second to anyone subscribed. I expect that there might be multiple subscribers to this Flux in the future, so I’ve included .share() to enable multicasting on the Publisher. In RxJava it might look something like this:

import java.util.concurrent.TimeUnit; 
import io.reactivex.rxjava3.core.Observable; 

public final class RxJavaSensor { 

    public static final Flowable<Float> TEMPERATURES = 
            Flowable.interval(1, TimeUnit.SECONDS) 
                    .map(ignored -> getCurrentTemperatureCelsius()) 
                    .share();  

    private static float getCurrentTemperature() { 
        //Left blank 
    } 
} 

They look pretty similar right? Both frameworks use Java native notation for the interval period, they both produce a Publisher that emits an incrementing counter and both Publishers have a .map() method that we can pass a lambda function to transform emitted items, and finally both implement the .share() method to enable multicasting. It is actually no surprise that these frameworks look so much alike since RxJava fully and Project Reactor mostly implements the Reactive Extensions API.

So where are they different

The main difference is their compatibility with different platforms. Where RxJava takes a minimalist approach priding itself on being light-weight and providing hassle-free android development, Reactor focuses on server-side development with seamless reactive networking capabilities. The way Reactor is integrated with Spring WebFlux makes it a no brainer to use in the context of a Spring application, and the way RxJava handles UI events on android makes it a no brainer in that context.

The role for Java Flow

Since both frameworks have been set up in the era of Java 8 they have had to bring their own interfaces, and since the release of Java 9 they provide adapters to the Flow API. So, in theory you could pass items from a Reactor Flux to an RxJava Flowable via the Flow API, but in practice you’ll probably stick to one implementation per application and use the interfaces that implementation provides. You could implement a super light-weight web content consumer made using the reactive http client from Java 11 and find yourself implementing your own BodySubscriber. But the ease of development on a WebFlux based application probably outweighs the benefits of your custom implementation in the long run. In short, Java Flow might have some use cases under very specific circumstances, but in my opinion, it is too little and too late.

Wrap up

Starting development on a reactive application in the current ecosystem is well supported and well documented, whichever framework you decide to use. But tweaking and optimizing it will take a lot more in-depth knowledge of your application and its context. Things like deciding what strategy to use when handling backpressure, or how your application will signal its processing capacity to your infrastructure so it can scale accordingly is where application developers like us are needed to implement fully reactive systems. That’s why we need a fundemental understanding of what it means to develop a reactive program and system, and I hope this post gives those of us who are just starting out a couple of leads to investigate.

Gijs van Horn
Gijs van Horn