RxJava: Essence, Components, and Reactive Programming

Author: IT Sectr Published: 2026-05-03 Reading time: 10 min

RxJava is a reactive programming library for the JVM that implements asynchronous data streams through the Observable pattern with functional transformation operators. It ports ReactiveX concepts to Java and Kotlin, providing a unified API for working with network requests, databases, UI events, and background tasks. According to ReactiveX, 2025, the library is used in over 120,000 projects on GitHub and is the reactive programming standard for Android until the arrival of Kotlin Flow. RxJava replaces AsyncTask, Loader, and callbacks with a single data processing chain.

Key Takeaways

  • RxJava is a ReactiveX implementation for Java/Kotlin with Observable, Flowable, Single, Completable, and Maybe types
  • Observable represents a data stream with backpressure management via Flowable when subscribing on a slow consumer
  • Operators map, flatMap, switchMap, zip, and combineLatest transform and combine asynchronous streams without blocking
  • Scheduler — Schedulers.io(), computation(), mainThread() manage which thread executes work and subscription
  • RxAndroid adds AndroidSchedulers.mainThread() for updating the UI from reactive chains

What is RxJava?

RxJava is an implementation of the ReactiveX (Reactive Extensions) library for the Java Virtual Machine. The first version of RxJava was released by Netflix in 2013 for managing asynchronous calls in server-side applications. At the time of its creation, the main alternatives in Java were Future and Callback — both approaches led to callback-hell and complex thread management. RxJava introduced composition of asynchronous operations through Observable with chains of functional operators.

The RxJava architecture is based on the Reactive Streams specification — a standard for asynchronous stream processing with non-blocking backpressure. The specification defines four interfaces: Publisher, Subscriber, Subscription, and Processor. RxJava 2+ fully implements Reactive Streams through the Flowable type, complying with backpressure contracts unlike RxJava 1. Observable in RxJava 2 does not support backpressure — it is intended for streams with a small number of events or UI events.

According to the JetBrains, 2025 survey, RxJava is among the top 3 libraries for Android development. Main use cases include: handling network requests via Retrofit (integrated with RxJava through CallAdapter), working with Room (reactive queries return Flowable or Maybe), animations and UI events via RxBinding, and debounced search on text input. All these scenarios share a common chain pattern: source (Observable) → transformation (operators) → subscription (subscribe).

RxJava Version History

RxJava 1 (2013) laid the foundation with Observable and operators but suffered from backpressure issues — in fast streams, data accumulated in memory, causing OutOfMemoryError. RxJava 2 (2016) fixed the architecture by separating Observable (without backpressure) and Flowable (with backpressure). RxJava 3 (2020) added Java 8 Stream API support, additional operators, and improved subscription performance. Currently, RxJava 3 is the recommended version for new projects.

Types of Reactive Streams in RxJava

RxJava provides five main types of reactive sources, each designed for a specific scenario. Observable and Flowable emit multiple values, Single emits one value or an error, Completable emits only completion without data, and Maybe emits one value, zero, or an error. Choosing the correct type reduces code volume and makes the chain self-documenting.

TypeNumber of EventsBackpressureScenario
Observable0..N, then completeNoUI events, short streams
Flowable0..N, then completeYesNetwork responses, database streams
SingleExactly 1 or errorNoHTTP request, reading one record
Completable0 (completion only)NoDatabase write, sending an event
Maybe0, 1, or errorNoCache: value exists or not

Flowable is the most flexible type for working with large data streams. It implements the Reactive Streams Publisher with backpressure support: the consumer can request a specific number of elements via Subscription.request(n). This prevents buffer overflow when producer and consumer speeds do not match. If backpressure is not critical, use Observable — it has less overhead due to the absence of the request mechanism.

Single is the optimal choice for HTTP requests. Retrofit 2 with RxJava CallAdapter returns Single<ResponseBody> for each request. Single guarantees exactly one call to onSuccess or onError, matching the semantics of an HTTP request — one response or one error. Completable is used for write operations that do not return data: insert, update, delete. Maybe is convenient for cache checking — it may return a value or not.

kotlin
// Example of using Single for an HTTP request
interface ApiService {
    @GET("users/{id}")
    fun getUser(@Path("id") userId: Int): Single<User>
}

// Subscription with processing on the main thread
apiService.getUser(42)
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe({ user ->
        textView.text = user.name
    }, { error ->
        Log.e("API", "Error: ${error.message}")
    })
    .addTo(compositeDisposable)

Transformation and Stream Management Operators

Operators in RxJava are higher-order functions that take one reactive source and return another, transforming the data stream. RxJava 3 contains over 400 operators, divided into categories: transformation, filtering, combining, error handling, and time management. Each operator is lazy — the chain is built at declaration and executed upon subscription.

Transformation Operators

map is the basic operator that transforms each value through a function. flatMap takes a function that returns an Observable for each element and flattens the result into a single stream. switchMap is similar to flatMap, but when a new element arrives, it unsubscribes from the previous Observable. concatMap preserves element order — unlike flatMap, it sequentially subscribes to each nested Observable.

kotlin
// JSON parsing with transformation and filtering
apiService.getUsers()
    .flatMap { users ->
        Observable.fromIterable(users)
    }
    .filter { user ->
        user.age >= 18
    }
    .map { user ->
        UserDto(user.name, user.age)
    }
    .toList()
    .subscribeOn(Schedulers.computation())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe({ adapter.submitList(it) },
               { Log.e("Error", it.message) })

Stream combining is an area where RxJava particularly excels. zip combines elements from multiple Observables pairwise by index: first with first, second with second. combineLatest emits a new value when any stream changes, combining the latest values from all streams. merge combines multiple Observables into one, preserving the order of event arrival. concat sequentially subscribes to each Observable and passes all its events before moving to the next.

Time management includes debounce (waiting for a pause in the stream before emitting), throttleFirst (emitting the first event, ignoring the rest within a window), timeout (error if no event arrives within the interval). Debounced search on text input is the most common scenario: searchObservable.debounce(300, MILLISECONDS).distinctUntilChanged() prevents unnecessary requests during fast typing.

CategoryOperatorBehavior
Transformationmap / flatMap / switchMapTransform a single value or stream
Filteringfilter / distinct / takeSelect values by condition
Combiningzip / combineLatest / mergeCombine 2+ streams
ErrorsonErrorResumeNext / retryRecover from failures
Utilitiesdelay / timeout / debounceTime management in streams

Schedulers and Multithreading

Scheduler in RxJava is an abstraction over a thread pool. The library provides five built-in Schedulers: Schedulers.io() for I/O operations (network, files), Schedulers.computation() for CPU-intensive tasks, Schedulers.newThread() for a new thread each time, Schedulers.single() for single-threaded execution, and Schedulers.trampoline() for immediate execution in the current thread.

subscribeOn and observeOn

subscribeOn determines which Scheduler executes the source Observable. If there are multiple subscribeOn calls in the chain, the one closest to the source takes priority. observeOn switches the downstream to the specified Scheduler — each observeOn call changes the thread for subsequent operators. A typical Android pattern: subscribeOn(Schedulers.io()) for network operations, observeOn(AndroidSchedulers.mainThread()) for UI updates.

java
// Multithreaded processing with context switching
Observable.fromCallable(() -> database.getItems())
    .subscribeOn(Schedulers.io())            // DB on io
    .map(items -> processItems(items))     // transformation on io
    .observeOn(Schedulers.computation())    // switch to computation
    .map(processed -> compressImages(processed))
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(result -> ui.showResult(result))

AndroidSchedulers.mainThread() is a Scheduler from the RxAndroid library that executes code on the Android main thread. It is mandatory for any UI updates in a reactive chain. The library uses Handler internally and guarantees execution on the UI thread even under high load. For background operations, Schedulers.io() supports an unlimited thread pool and is suitable for any blocking operations. Schedulers.computation() uses a fixed pool equal to the number of CPU cores.

RxJava in Android: Practical Applications

RxJava in Android is used for three main scenarios: reactive queries to Room, integration with Retrofit, and reactive UI binding via RxBinding. Each scenario has its own set of types: Room returns Flowable for observable queries, Retrofit returns Single for HTTP requests, RxBinding returns Observable for UI events.

Room + RxJava

Room is a persistence library from Google. Starting with Room 2.1, the database supports reactive return types: Flowable and Observable. When any record in the table changes, Room automatically sends a new value to the stream. The developer subscribes to Flowable in the ViewModel and receives up-to-date data without manual queries on each change.

kotlin
// Room DAO with reactive query
@Dao
interface UserDao {
    @Query("SELECT * FROM users WHERE id = :id")
    fun getUserById(@Param("id") userId: Int): Flowable<User>

    @Insert
    fun insertUser(user: User): Completable
}

// ViewModel — Room + Network composition
class UserViewModel(private val dao: UserDao) : ViewModel() {
    val users: Flowable<List<User>> = dao.getAllUsers()
        .subscribeOn(Schedulers.io())
}

The MVVM + RxJava pattern is built on the ViewModel having no references to the View. The ViewModel publishes reactive sources (Flowable, LiveData via Transformations), and the Activity or Fragment subscribes to them. This provides testability: the ViewModel is tested without the UI by substituting Schedulers via RxJavaPlugins.setComputationScheduler. CompositeDisposable in the ViewModel manages the subscription lifecycle — on onCleared(), all subscriptions are cancelled.

RxJava vs Kotlin Flow

Kotlin Flow is a native implementation of cold streams in Kotlin, built into coroutines and introduced in Kotlin 1.3. Flow solves the same problems as RxJava but with fundamental differences: built-in coroutine support (suspend functions), cancellation via coroutine cancellation, and no backpressure issues — Flow uses suspend instead of buffering. Flow is part of the Kotlin standard library, requiring no additional dependencies.

RxJava remains the preferred choice for Java projects, projects supporting Java 7-8, and existing RxJava codebases. The RxJava ecosystem is significantly richer: over 400 operators versus about 50 in Flow, integration with Retrofit via a built-in CallAdapter, backpressure support via Flowable, and RxBinding, RxPermissions, RxLocation for Android. Kotlin Flow is rapidly catching up, but RxJava's flexibility in complex stream combining scenarios is still higher.

CharacteristicRxJavaKotlin Flow
LanguageJava / KotlinKotlin only
CancellationDisposable / CompositeDisposableCoroutine cancellation
BackpressureFlowable (BUFFER, DROP, LATEST strategies)Via conflate / buffer
Operators400+~50 (extensible)
Room integrationFlowable, ObservableFlow, StateFlow
ViewModelCompositeDisposableviewModelScope + Flow

Frequently Asked Questions

What is the difference between Observable and Flowable in RxJava?

Observable does not support backpressure — if the producer is faster than the consumer, events accumulate in memory. Flowable implements Reactive Streams with backpressure via Subscription.request(), preventing buffer overflow when speeds do not match.

When should I use Single instead of Observable?

Single is used for operations that return exactly one value or an error: HTTP requests, reading a single record from a database, computing a result. Single semantically corresponds to Future and reduces code by removing unused onComplete.

How do I cancel a subscription in RxJava?

The dispose() method on Disposable cancels a subscription. For group management, CompositeDisposable is used — it collects all Disposables and disposes them simultaneously on clear(). The typical place is onCleared() in ViewModel or onPause() in Activity.

What is the difference between flatMap and switchMap?

flatMap subscribes to all nested Observables and merges their events in arbitrary order. switchMap unsubscribes from the previous Observable when a new element arrives and subscribes to the new one. switchMap is used in search — each new request cancels the previous one.

Should I migrate from RxJava to Kotlin Flow?

For new Kotlin projects, Flow is preferable due to coroutine integration and smaller size. For existing RxJava projects, migration is justified only if the entire codebase is moving to coroutines — using both libraries intermediately complicates the architecture.

Summary

  • RxJava is a ReactiveX library for the JVM with Observable, Flowable, Single, Completable, and Maybe types for different scenarios
  • Flowable supports backpressure via Reactive Streams to prevent overflow when speeds do not match
  • Operators map, flatMap, switchMap, zip, combineLatest, debounce provide declarative stream processing
  • Schedulers io(), computation(), mainThread() manage execution threads without blocking the UI
  • RxAndroid integrates RxJava with Android by providing AndroidSchedulers.mainThread() and simplifying UI updates
  • Kotlin Flow is a native alternative with coroutine integration, but RxJava retains an advantage in operator ecosystem
  • MVVM + RxJava is a standard Android development pattern with a ViewModel separated from the UI and reactive subscriptions

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