Change language

Which to choose: Coroutines or RxJava?

| |

Comparing the pros and cons of frameworks.

Which framework to choose for your project - Coroutines or RxJava? To help answer this question, we translated for you an article about the pros and cons of both frameworks.

At Trello we were thinking of switching from RxJava to Coroutines. We were already using coroutines here and there, but until recently we didn't consider them as a full-fledged replacement for Rx, mainly because of the poor functionality of coroutines. Now, with the addition of StateFlow and SharedFlow, coroutines can be compared to Rx.

To justify switching from Rx to coroutines, I've written a list of pros and cons for each framework.

Advantages of RxJava

A mature and proven framework

RxJava works perfectly in our project and many others. We've been using Rx in our application for a long time and have successfully augmented the framework with our own functionality. We have a lot of experience using Rx, and we know all the subtleties of the framework and possible bottlenecks.

Coroutines is a new and untested technology. We are not sure that it will work perfectly in each and every case. Switching to coroutines will require time to learn and master the best approaches in using them specifically for our application. Plus we will need to rewrite some auxiliary tools to use coroutines instead of Rx. We don't know how coroutines behave in borderline cases and what their weaknesses are.

Stability

The RxJava API is stable.

There are many APIs in coroutines marked as experimental, and they may change in the future.

It's easier to look for bugs

RxJava is notoriously difficult to debug, but stackrays coroutines will give even Rx a head start. We already have experience debugging Rx, and will have to retrain for coroutine (although there are tools for debugging coroutine).

Compatibility with Java

RxJava can be used with any Java-compatible language, while coroutines can only be written in Kotlin. For our project this isn't a problem, but it could be a problem for others. (Note that I'm talking about writing code, not using it: any third-party library can use coroutines under the bonnet, and expose a perfectly normal Java API on the outside.)

Benefits of coroutines

Kotlin Multiplatform

RxJava, as the name implies, is limited to the Java language. Coroutines work on any platform that supports Kotlin, so if we suddenly need to reuse asynchronous code between Android and iOS, we can safely use coroutines.

Simple API

Rx has many different types of threads (Observable, Flowable, Maybe, Single, Completable) for each case. Kotlin duplicates all this functionality with two types: suspend fun and Flow. This solution is much easier to use not only because there are only two types, but also because you can call asynchronous functions via simple lambda expressions (as opposed to combining different threads via many sometimes non-obvious operators in Rx).

Any toolkit written on top of coroutine will be easier to create. For Rx, you need to write the same code for each type five times, by the number of thread types.

Simpler operators

Writing custom operators in Rx is a separate headache. To do it really well, you need to understand how Rx works under the bonnet (e.g. understanding back-pressure), which in itself is a great achievement, conquered by only one person (I'm almost not kidding here). On the other hand, new operators for Flow are written like the rest of the code. Compare the full class for Rx map() operator and the analogous operator (a couple of lines of code) for Flow.

Structured multithreading

Rx threads are prone to leaks, while coroutines handle incoming objects even if you forget about them. Kotlin coroutines are structured (separated by scope and application), which makes managing the lifecycle of open threads much easier.

Easy back-pressure management

Back-pressure (an overload of incoming events) occurs when a data source outputs that data faster than the receiving thread has time to process it. This is a real problem in Rx and a source of redundant and confusing code in the framework, because receiving threads must constantly communicate with emitting threads to avoid event overload.

Flow from coroutine handles back-pressure much more simply by deferring the emitting threads for a while if the incoming data becomes too much at a particular point in time. You don't have to deal with back-pressure as often as with Rx.

Performance

Flow has as good (if not better) performance than Rx. Reactive Scrabble is the standard for testing flow performance, and Kotlin has a version of Scrabble in their repository. Based on measurements (the lower, the better), Flow coroutine outperforms RxJava.

Interoperability

There is good interoperability between coroutines and Rx. Kotlin provides different converters from Rx to Flow. Without their help, you would have to rewrite all the code manually - such a prospect. With code converters, you can translate Rx to coroutines in stages. This also means that you can continue to use Rx-based libraries in a project that runs on coroutines.

Coroutines also have tools to migrate from legacy Rx threads to analogues in Flow. With this tool, you simply migrate the Rx thread to Flow, and the replacement is done automatically in the development environment. (Note that this works specifically for legacy streams, not just migrating all Rx to Flow.)

To conclude

The main problem with migrating from Rx to coroutines is that it's a new untested technology, with all that implies. You'll have to get used to it, adapt to it, develop your own approaches and solutions and customize them. There is no doubt that you will miss some large bug (or even two or three) simply because you do not know all the nuances in specific cases.

But these are all temporary problems. Coroutine has great advantages: multiplatform, simple API, structured multithreading. You will get more benefits in the long run. For me personally, it's a choice between mature technology and functionality. Coroutines will become the same mature and established technology, but Rx will never be able to replicate some of the unique features of coroutines.

My team and I will gradually begin to transition to coroutines. As with any major change, we will switch to coroutines in stages, just in case of unforeseen problems. So far, so good, it seems. Given all of the above and your attitude to risk, you may not change from the familiar and familiar Rx to coroutines until they are more stable and proven.