Reactive Programming with RxJava: Future Directions

Because we took a long time in the 0.x phase before locking down the APIs in RxJava 1.0, it is a fairly mature and stable release. Also, as a result of our decision to support Experimental and Beta markers on APIs, ongoing experimentation can continue before promoting an API to Final. However, the 0.x/1.x phase still ended up with a few decisions that warrant a breaking release; hence, a version 2.0 is being worked on.

Fundamentally, it will be very similar to 1.x, so it won’t require much change in your thinking nor will it be a significant change for usage. Even as 2.0 is released, this book will still apply in most regards. So why a version 2?

Reactive Streams

The first reason is to natively support the Reactive Streams API. Despite the RxJava team being involved in the collaboration that led to Reactive Streams, RxJava v1 APIs were already locked in and couldn’t change to adopt the interfaces in Reactive Streams. Thus, RxJava v1 requires an adaptor, even though it semantically behaves mostly like Reactive Streams. Version 2 will directly implement the Reactive Streams types and comply with the spec so as to better support interoperability across the Java community.

Observable and Flowable

Another reason is to split the Observable type into two types: Observable and Flowable. It was a mistake to make everything require backpressure because not all use cases warrant it. It has a slight performance overhead, but the primary reason why this was a mistake is that it adds significant mental complexity to using Observable and greatly increases the difficulty of creating custom operators.

Pure push use cases should be able to use Observable as originally designed by Erik Meijer without considering the request(n) semantics of Reactive Streams. These use cases are quite common. Basically, all user interface (UI) use cases, such as on Android, are pure push; the use of request(n) is confusing at best and unnecessarily complicates things. Yes, the onBackpressureDrop style operators can be quite useful in these cases, but those should be opt-in.

Thus, version 2 is going to return Observable back to being pure push without request(n) and it will not implement the Reactive Streams types or spec. A new type, Flowable will be added, which will be the “Observable with backpressure” that implements the Reactive Streams Publisher type and spec. The name “Flowable” was inspired by the Java 9 java.util.concurrent.Flow, which adopts the Reactive Streams interfaces.

Having Observable and Flowable will also better communicate in public APIs what the behavior of the data source is. If it is an Observable, it will push and the consumer must be ready. If it is a Flowable, it will do pull-push and only send as many items as requested by the consumer. Bridging between them will be possible, similar to how RxJava v1 does it, but it will be far more explicit, such as observable.toFlowable(Strategy.DROP) which converts an Observable into a Flowable with the appropriate backpressure strategy to apply if data is pushed faster than the consumer can handle.

Performance

The last major reason for version 2 is the ability to improve overall performance (reduce overhead) as it is no longer bound by the architectural limits of the version 1 design. This is partly achieved by reducing the allocation amount when building up chains of operators, subscribing to, and running them. By default, Subscribers are no longer wrapped into a SafeSubscriber (Flowable.safeSubscribe() is provided for that) and there is no longer a need to cancel (unsubscribe in version 2 terminology) the chain on a terminal event.

The second source of performance improvements is an internal optimization methodology called operator-fusion (which extends the Reactive-Streams protocol), greatly reducing the backpressure and queue-management overhead in many typical synchronous flow setups (and sometimes in asynchronous flows as well). In some benchmarks, throughput with backpressure-enabled flows are only 20–30% slower than Java 8’s Stream (which is synchronous pull) implementation compared to the 100–200% slower throughput of version 1.

Migration

Because RxJava is heavily entrenched in applications, a breaking change would be very difficult to adopt. Thus, version 2 is going to have a different package name and Maven artifact IDs so that both version 1 and version 2 can coexist in an application.

1
2
v1 package v2 package v1 Maven v2 Maven
rx.* io.reactivex.* io.reactivex:rxjava io.reactivex.rxjava2:rxjava

Migrating from RxJava version 1 to version 2 will primarily come down to the following:

  1. Changing package from rx. to io.reactivex
  2. If backpressure is unwanted, changing from Observable to Flowable

RxJava v2 resides in the 2.x branch on GitHub, and the DESIGN.md document is an effort by the community to capture the design decisions for version 2. Further information on the differences between versions 1 and 2 can be found on GitHub.