RxJava: Basics, ReactiveX, and Working with Data Streams

Author: IT Sectr Published: 2026-03-16 Reading time: 8 min

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 — Java implementation of ReactiveX for asynchronous data stream processing
  • Observable — data source that emits elements to an Observer
  • Observer — subscriber receiving onNext, onError, and onComplete notifications
  • Operators — chain of functions for transforming, filtering, and combining streams
  • Schedulers — component for managing execution threads of Observable and Observer

What is RxJava and ReactiveX

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.

Observer Pattern in RxJava

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 Types: Observable, Flowable, Single, Maybe, Completable

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.

TypeElementsBackpressureUse Case
Observable0..NNoUI events, small streams
Flowable0..NYesBig data, real-time
Single1 (onSuccess/onError)Single response (network)
Maybe0..1Optional value (cache)
Completable0 (onComplete/onError)Operation without data (write)

Single, Maybe and Completable

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.

RxJava Operators: Stream Transformation and Filtering

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.

  • map — transforms each element (Integer → String)
  • flatMap — transforms an element into an Observable and merges all into one stream
  • filter — passes elements that satisfy a condition
  • zip — combines elements from N Observables by index
  • merge — merges multiple Observables into one, preserving chronological order
  • debounce — emits elements only if a specified time span has passed without another emission

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.

Error Handling with Operators

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: Thread Management in RxJava

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).

RxJava Code Examples in Android

Let us consider three scenarios: a network request with Single, parallel requests with zip, and debounce for a search field with debounce.

Network Request with Single

Single is perfect for Retrofit requests: one request — one response. Subscribe on the main thread for UI updates.

java
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); }
    })

Parallel Requests with zip

zip combines results of two independent Singles into one. They execute in parallel, the result is produced after both complete.

java
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 for Search Field

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.

java
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 vs Kotlin Coroutines: Comparing Approaches

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.

  • RxJava — reactive, data stream, >200 operators, push-based, steep learning curve
  • Coroutines — sequential, suspend/await, ~40 functions, pull-based, simple syntax
  • RxJava — mature (2016), huge ecosystem, but steep learning curve
  • Coroutines — modern (2018), Google’s preferred choice for new code
  • RxJava — built-in backpressure via Flowable, well-tested buffering strategies
  • Coroutines — Flow with backpressure is recent, but actively developed by JetBrains

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.

Migration Strategy from RxJava to Coroutines

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

How is Observable different from Flowable?

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.

What are subscribeOn and observeOn?

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.

Should I switch from RxJava to coroutines?

For new projects — yes, Google recommends coroutines. For existing projects — gradual migration via kotlinx-coroutines-rx3. RxJava remains stable and supported for legacy code.

How to handle errors in RxJava?

Through operators: onErrorReturn (default value), onErrorResumeNext (fallback Observable), retry (retry N times). Or via Observer.onError() to display to the user.

What is CompositeDisposable?

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

  • RxJava — reactive programming library for Java and Android based on the Observer pattern
  • Observable/Flowable — data sources with and without backpressure support
  • Single, Maybe, Completable — specialized types for 1, 0..1, and 0 elements
  • Operators (map, flatMap, zip, filter) — chain of transformations with over 200 functions
  • Schedulers — subscribeOn for source and observeOn for consumer
  • RxJava vs Coroutines — coroutines recommended by Google for new code, RxJava for legacy
  • CompositeDisposable — safe subscription management with cancellation on screen destroy

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.

Discuss the project

Read also