RxJava: 5 Steps to Reactive Enlightenment

Philip Leonard
Picnic Engineering
Published in
11 min readJan 10, 2019

--

During my time learning and using RxJava I’ve read numerous blog posts, watched countless hours of online talks and presentations, attended conferences and coffee chats and on a number of occasions I’ve attempted to boil down the most condensed approach for teaching its core concepts.

I have ultimately resided to the slightly demotivating fact that learning it simply requires grit and determination. There is no “learn RxJava in y minutes” guide, and if there were it would assume familiarity with some of the paradigm-shifting concepts that it is based upon.

Rather than attempt to teach it once again, this post aims to prepare and reassure you during your bumpy journey ahead.

Having (or even had) frustrations learning RxJava? Read on.

At Picnic, we have been using Reactive Programming to power concurrent processes for some time now. It is the reactive extensions libraries RxJava, and more recently Spring Reactor, which are our tools of choice. The RxJava wiki page states that:

RxJava is a Java VM implementation of ReactiveX (Reactive Extensions): a library for composing asynchronous and event-based programs by using observable sequences.

Sounds complex. A phrase that often makes an appearance in a discussion about RxJava is:

RxJava has a steep learning curve.

In fact, this phrase crops up more often than not whenever developers talk about complex technologies. The fact of the matter is, is that the term a “steep learning curve” implies that a skill can be acquired over a short period of time. Given that I myself have been misusing this term for so long, I decided to go back to the drawing board with this one. I searched high and low for inspiration and stumbled my way onto another rather crude conceptual chart that I feel better represents the RxJava learning process:

Kübler-Ross model

Most of us will have heard of the 5-stages of grief. It refers to a more formal model of how humans react emotionally to loss over time, known as the Kübler-Ross model. For the purpose of RxJava, I like to think of these emotional responses as a result of the grieving the absence of imperative programming. Reactive Programming itself is a shift in a number of familiar concepts, and picking them up can be an emotional rollercoaster ride. So please, strap yourself in.

Denial

We first ratchet up the first sharp incline that is the denial phase, we are clamped firmly in our seats, our arms are outstretched and our hands clasped firmly on the bar in front of us. Our knuckles as white as ivory we make our stand against the unknown.

“nananananaanana”

It came as no surprise to me that when trying to convince colleagues and classmates of the power of RxJava, that I was met with considerable questioning.

All good technologies have stood the test of pragmatic and practical questioning and RxJava is no exception.

Is concurrency so easy in Java?

Are you comfortable using Java’s concurrency primitives? Does that mean to say everyone you work with is? Does it mean to say you can’t make a slip up when juggling complex ideas such as locking, semaphores, atomicity, synchronisation and queuing? Isn’t it about time we simplify and abstract some of these concepts? While RxJava definitely doesn’t make concurrency trivial, many of these concepts are abstracted and even optimised for us under the hood.

Reactive Programming is becoming a first class citizen in Java

Since the inauguration of the Reactive extensions concepts in Java with RxJava over 5 years ago, we have seen these very same concepts formalised in the frameworks we use and even in the Java language itself:

With the adoption of the Flow API in Java 9, it looks clear that Reactive Programming and specifically the principle of Reactive Extensions in the JVM is here to stay.

We now teeter on the edge of denial before the weight of the front carriages begin to win the war and we are plunged deep into the RxJava experience.

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 Singles, Maybes and Completables 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:

Image result for 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.
  • No concurrency: 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 Flowables 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.

Acceptance

The ride is almost over. It cranks its way slowly up on its final ascent. We ascend above cloud level and are hit with a beam of sunshine. Our grip loosens further on the bar in front of us and our seat belt unbuckles. We all of a sudden feel a weightlessness, not the sickly nervous kind, rather the feeling of enlightenment.

A Language for Microservices

One major revelation that we had when making use of RxJava and Reactor is that reactive programming and microservices are a perfect marriage:

  • We are able to easily, but carefully, scale our single machine performance
  • We are able to parallelise previously sequential tasks with ease
  • With backpressure, we are able to harmonise load across our backend.
  • Service fault tolerance, retries and error handling are concepts built into RxJava and Spring Reactor.
  • Finally, we are able to make a stepping stone towards an event-driven platform.

We’ve got you covered

If you want to keep using RxJava, we are here to lend a hand. We have made some tools and utilities to fix context propagation and to make your RxJava life a little smoother.

https://github.com/PicnicSupermarket/reactive-support

However, if you are using RxJava like us to power backend services, it’s probably worth hedging your bets that Reactor is its successor. David Karnok, one of the chief maintainers for both projects puts it best:

--

--

Tech Lead @ Picnic. Reactive Programming, Observability & Distributed Systems Enthusiast.