Anger

The wind now rushes past our faces as we hurtle down the RxJava learning curve. We delve deep into the RxJava Flowable API. We’re overwhelmed by the choice we have. Once limited to ~10 functional operators in the Java Stream API, there now appears to be an operator for everything we would ever need. From flatMap() to buffer() and from debounce() to zip() , we are now spoiled for choice.

But how on earth do all of these work? Well, these Rx Marbles diagrams look nice…

Losing your Rx Marbles

For most intents and purposes the Rx Marbles diagrams that appear in the RxJava Javadoc help visually simplify a number of RxJava’s operators. Some concepts in RxJava are just too hard to simplify. For example, retryWhen() :

A little mind-boggling right? I for one only understood this diagram after understanding the operator in code. Not exactly the intention of a visual aid.

A topic that trips up a number of developers on their maiden voyage of using RxJava is the matter of scheduling. Using observeOn() and subscribeOn() operators we can define which Schedulers (thread pools) observe and subscribe operations are executed on:

🤯

What this wall of shapes is trying to express is the direction in which these operators effect the thread pool which we execute portions of the reactive stream on. Simply speaking: observeOn ↓ and subscribeOn ↑ (specifically to the source). To gain a true understanding, however, neither a diagram nor a few words are going to cut it.

Flowable

Reactive frameworks for the JVM generally aim to set out to achieve the same thing. Reactor, Akka, RxJava and V.ertx to name the better-known ones. A Publisher<T> is the interface that generalises their purposes and bridges these frameworks, allowing us to hop from RxJava and Reactor and back again at will. In RxJava however this only applies to the Flowable type:

Single , Maybe and Completable are syntactical sugar for RxJava. It makes the cardinality of a stream syntactically explicit, but this isn’t generic to other frameworks such as Spring Reactor. Our neat RxJava APIs which expose Single s, Maybe s and Completable s now require boilerplate code to make them reactive streams compatible.

Even with its complexity and frustrating design choices, the RxJava ride starts to slow. We are both enthralled and overwhelmed by the first big plunge. We understand the true power of reactive programming, but we have yet to evangelise.

Bargaining

We loosen our grip on the bar in front of us. We already take a peek up at the climb ahead of us and our mind begins to spin with the number of shortcuts we are thinking of taking to get there.

While RxJava is packaged simply as a framework for the JVM, it is often referred to as a language given that its primitives functional style quickly becomes prolific within a codebase.

As developers, when we embark on the journey of learning a new language, it is only natural that we carry old habits with us and take shortcuts that seem reasonable to us using what we know best. When moving from imperative programming to reactive programming, some of those shortcuts just look too delicious to pass up.

The Nest of Subscriptions

Ones with a bit of experience with async programming will be familiar with the notion of a callback. Ones with more experience with async programming will be familiar with the notion of callback hell:

Using RxJava it is easy to use subscriptions in the same way:

☠️

In fact, this is where asynchronous programming and RxJava diverge. RxJava provides us with a powerful set of operators such that we can functionally compose our streams. This means that we only need to handle at max one subscription when done correctly:

Learning: If you see multiple subscriptions in the same reactive chain, it can be written better.

The Nest of Blocking

flowable.blockingFirst()

This is the proverbial RxJava ejector seat. If you have hit the end of the async trail then this is a useful way of bridging the reactive and imperative worlds. However, they shouldn’t be used interchangeably with the reactive world:

ints.map(i -> i + flowable.blockingFirst());

This is even worse than nested subscriptions, as we are completely abandoning asynchronicity — thereby blocking the current thread.

Learning: Block only once in a reactive chain, if you must.

Reactive Sugar Coating

Each of the RxJava primitive types from Single to Flowable provide some static functions to help us bridge imperative code and reactive code. This is our ticket into the reactive world. It is however easy to abuse this free pass, taking code such as:

And wrapping it in a reactive kickstarter:

We now obtain a nice reactive type, with minimal effort. We can brag about this by hiding it behind an interface and exposing this API to consumers who will no doubt be very impressed with our reactive efforts.

While not necessarily immediately obvious, with such a coarse approach to using RxJava we lose out on its key benefits:

No reactivity : our block of code is imperative, emitting only a single event.

: our block of code is imperative, emitting only a single event. No concurrency : scheduling is coarse. Calls to backend services and data sources are still serial using this construct.

: scheduling is coarse. Calls to backend services and data sources are still serial using this construct. No backpressure: we are unable to emit backpressure signals and we risk exhaustion of upstream resources.

👀Let’s take a peek under the hood 👀

Learning: To benefit fully from RxJava, our code must be reactive from head to toe.

Of course, some middle ground is acceptable if not at times necessary in a real-world scenario, especially when it comes to consuming non-reactive 3rd party libraries and frameworks.

Depression

We sit at the dip of the ride. Our lunch returns to its usual position as we decelerate after the plunge. We are overwhelmed but smug about the acquisition of a new skill. We have mastered RxJava at a programmatic and syntactical level. We are wielding Flowable s willy-nilly and reducing previously complex webs of callbacks down to streamlined reactive streams. We use scheduling to optimise and parallelise streams with ease…. until we deploy to production.

Destroying Performance With Scheduling

Imagine the following scenario. We have a Spring Boot service that exposes three endpoints. All invoke the same mockIO() call which sleeps the current thread for 500 ms as if our application had delegated some operation to an IO source such as a DB or another REST service:

These endpoints allow us to asses the differences in scheduling, putting our application under heavy load using Gatling.

/blocking — 382 req/sec

In the first call, we leave the reactive world and we leave the execution down to the main thread:

/ scheduled — 1724 req/sec

In the second call, we schedule correctly on the IO scheduler. This elastic sized thread pool grows as we schedule more tasks. Given that our operation requires CPU time only to check the clock, we can acquire a vastly larger number of requests.

/ bad-scheduling — 16 req/sec

The computation scheduler thread pool is of an equivalent size to the number of available processors — Runtime.getRuntime().availableProcessors() . Therefore 8 on my 8 core machine. This is much smaller than our main application thread pool, and therefore our I/O operation is scheduled with far more CPU time than is necessary.

As we accept connections on a web server thread pool (provided by Netty/Jetty/Tomcat) and introduce the bottleneck at the point of scheduling on the computation scheduler, we in fact not only process fewer requests, but those that we do also have an average response time of 5x that of our vanilla blocking example.

While here it seems pretty damn obvious we are doing something wrong, you can imagine that more complex scheduling constructs might slip through the code review process.

It’s easy to convert your pimped out Rx ride into a smouldering heap

Learning: In RxJava, scheduling must be fine-grained.

Contextless Programming Language

Switching threads in RxJava means that we lose thread local contexts as there is no out of the box support for propagating them. Meaning that when we invoke a seemingly harmless scheduling operation, we lose logging information, tracing information, security contexts and any other thread local variable.

Learning: RxJava cannot abstract away all of Java’s concurrency primitives.

Spring Reactor

Just as you are beginning to see some return on your investment of learning RxJava, in steps Spring Reactor.

A new framework? A new syntax? New primitives? Yes, but not really. Spring Reactor is certainly an improvement over RxJava 2 for usage in the backend, but fortunately, its API surface, primitives and core concepts are similar if not identical. Some of the benefits are that context propagation is handled during the composition of streams with Flux.subscriberContext() and that it is a core component of Spring 5 and Spring WebFlux — although these frameworks are also transparent to both.

In fact interoperability between Reactor and RxJava is trivial, and we can always use the common Publisher interface and do it ourselves. One downside is that we lose the ability to express stream cardinality syntactically:

Our syntactical sugar is somewhat dissolved.