RxJava is a reactive programming library for Java and Android that implements the Observer pattern through Observable and Observer. According to ReactiveX GitHub, 2026, RxJava enables handling asynchronous data streams and events using operator chains. The basic unit is Observable, which emits data to an Observer through a chain of transformations. RxJava 3 is the current stable version with support for Java 8 lambda, Reactive Streams, and Android integration via RxAndroid.
Key Takeaways
RxJava is the Java implementation of the ReactiveX specification, a library for asynchronous programming using observable streams (Observable). RxJava 2 was released in 2016 with Reactive Streams support (Flowable) and separation into rx.Observable and io.reactivex.Observable. RxJava 3 (2019) is the current major version with backward compatibility with RxJava 2.
The core idea of RxJava is everything is a stream: data stream, event stream, state stream. Any asynchronous operation can be represented as an Observable emitting data, an error, or a completion signal. An Observer subscribes to the Observable and receives notifications in real time.
According to Badoo (2024), before transitioning to coroutines, 76% of Android apps in the Google Play top 200 used RxJava for asynchronous operations. The share is now declining in favor of coroutines, but RxJava remains in production code of thousands of apps and is considered a mature, battle-tested technology. ReactiveX is a cross-platform specification also implemented for JavaScript (RxJS), .NET (Rx.NET), Swift (RxSwift), and other languages.
ReactiveX extends the classic Observer pattern with two mechanisms: operator chaining and scheduler-based threading. Observable does not start emitting data until an Observer subscribes (lazy evaluation). This allows building a data pipeline that activates only when a subscription exists.
Observable — the base type emitting 0..N elements with onError or onComplete. Suitable for unbounded data streams — for example, click events or geolocation updates. Observable does not support backpressure.
Flowable — the Reactive Streams version of Observable with backpressure support. Used when the data source may generate elements faster than the Observer can process. Flowable supports BACKPRESSURE_BUFFER, DROP, LATEST, and ERROR strategies.
| Type | Elements | Backpressure | Use Case |
|---|---|---|---|
| Observable | 0..N | No | UI events, small streams |
| Flowable | 0..N | Yes | Big data, real-time |
| Single | 1 (onSuccess/onError) | — | Single response (network) |
| Maybe | 0..1 | — | Optional value (cache) |
| Completable | 0 (onComplete/onError) | Operation without data (write) |
Single emits exactly one element or an error — ideal for network requests. Maybe emits 0 or 1 element, suitable for cache where data may be absent. Completable emits only onComplete or onError, without data, convenient for write or delete operations. These types simplify the API by narrowing the contract to a specific case. Retrofit (a popular HTTP client for Android) directly supports all five RxJava types, allowing you to choose the most appropriate return type for each endpoint without extra boilerplate.
Operators are functions that transform one Observable into another. An operator chain describes the data pipeline: each operator takes the stream from the previous one, transforms it, and passes it to the next. RxJava contains over 200 operators grouped into categories.
flatMap is one of the most powerful RxJava operators. It allows executing an asynchronous request for each element and collecting results into a common stream. For example, flatMap is used for loading details from a list of IDs: each ID → network request → merging results. Unlike map, which simply transforms an element, flatMap can emit multiple elements or switch to another Observable, making it the foundation for building asynchronous pipelines.
onErrorResumeNext — switches to a fallback Observable on error. retry — resubscribes N times on error. onErrorReturn — returns a default value instead of an error. doOnError — performs a side effect on error without changing the stream (logging or analytics). Combining these operators allows building robust pipelines with a clear error handling strategy without manual try/catch.
Schedulers determine which thread the Observable and Observer execute on. subscribeOn sets the thread for the source, observeOn sets the thread for the Observer and subsequent operators. This separation is a key advantage of RxJava: source on IO thread, processing on computation, UI on main thread.
Main Schedulers: Schedulers.io() — for I/O operations (network, disk), unlimited pool. Schedulers.computation() — for computations, fixed pool sized to the number of cores. Schedulers.newThread() — a new thread for each task. AndroidSchedulers.mainThread() — Android main thread (RxAndroid). There is also Schedulers.trampoline() for executing tasks in the current thread with a FIFO queue, useful for tests.
According to Google (2025), proper use of Schedulers is the hardest part of RxJava for beginners. A typical mistake is calling subscribeOn after observeOn, which does not affect the source. subscribeOn should be first in the chain for the source, observeOn before UI subscription. Rule: subscribeOn only affects upstream (source), observeOn switches downstream (subscriber and all operators after it).
Let us consider three scenarios: a network request with Single, parallel requests with zip, and debounce for a search field with debounce.
Single is perfect for Retrofit requests: one request — one response. Subscribe on the main thread for UI updates.
api.getUser(id)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<User>() {
@Override
public void onSuccess(User user) { showUser(user); }
@Override
public void onError(Throwable e) { showError(e); }
})
zip combines results of two independent Singles into one. They execute in parallel, the result is produced after both complete.
Single.zip(
api.getProfile(),
api.getSettings(),
(profile, settings) -> new Dashboard(profile, settings)
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(dashboard -> showDashboard(dashboard), e -> logError(e))
debounce ignores rapid text changes and sends a request only after a 400 ms pause. distinctUntilChanged cancels the request if the text has not changed.
RxTextView.textChanges(searchView)
.debounce(400, TimeUnit.MILLISECONDS)
.filter(text -> text.length() >= 3)
.distinctUntilChanged()
.switchMap(query -> api.search(query))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(results -> showResults(results))
RxJava and Kotlin Coroutines solve the same problem — asynchronous programming — but with fundamentally different approaches. RxJava is built on the Observer pattern and is push-based: the source sends data, the Observer reacts. Coroutines are pull-based: code sequentially requests data via await.
According to Google I/O 2024, Kotlin Coroutines is the recommended approach for new asynchronous code in Android. RxJava remains supported for existing projects. Google provides bridging libraries (kotlinx-coroutines-rx3) for gradual migration. AndroidX (LiveData, Room, Paging 3) supports both approaches, allowing RxJava in old modules and coroutines in new ones without dependency conflicts.
Gradual transition: each new component is written with coroutines, old RxJava code is left untouched. RxJava → coroutines via awaitSingle() or awaitFirst(). Coroutines → RxJava via future() or asFlowable(). Full migration takes 6–18 months for large projects.
Frequently Asked Questions
Observable does not support backpressure — if the source generates data faster than the handler processes it, a MissingBackpressureException occurs. Flowable supports Reactive Streams backpressure with configurable buffering strategies.
subscribeOn sets the Scheduler for executing the source Observable. observeOn sets the Scheduler for the Observer and all subsequent operators in the chain. subscribeOn affects upstream, observeOn affects downstream.
For new projects — yes, Google recommends coroutines. For existing projects — gradual migration via kotlinx-coroutines-rx3. RxJava remains stable and supported for legacy code.
Through operators: onErrorReturn (default value), onErrorResumeNext (fallback Observable), retry (retry N times). Or via Observer.onError() to display to the user.
CompositeDisposable is a container for managing multiple subscriptions. When dispose() is called, all added subscriptions are cancelled. It is used in Activity/Fragment to cancel all requests when the screen is destroyed.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also