Grasping Reactive Programming in Java: A Basic Guide with Reactor


4 min read

The Reactor is an implementation of the reactive programming paradigm.

Reactive programming is an asynchronous programming paradigm concerned with data streams and the propagation of change. This means that it becomes possible to express static (e.g. arrays) or dynamic (e.g. event emitters) data streams with ease via the employed programming language(s).

Over time, a standardization for Java emerged through the Reactive Streams initiative, which is a specification that outlines a set of interfaces and interaction guidelines for reactive libraries on the JVM. These interfaces have been incorporated into Java 9 under the Flow class.

In reactive streams, there is a Publisher-Subscriber pair. The Publisher notifies the Subscriber of newly available values as they arrive, with this push aspect being crucial to the reactive nature. Additionally, operations applied to pushed values are expressed declaratively rather than imperatively: the programmer conveys the logic of the computation rather than detailing its precise control flow. We will soon look at the communication between Publisher-Subscriber.

Why do we need an asynchronous reactive library in the first place?

Modern applications often cater to vast numbers of concurrent users, and despite the continuous advancements in modern hardware, software performance remains a critical concern.

There are generally two methods to enhance a program's performance:

1. Parallelize to utilize more threads and additional hardware resources. 2. Pursue greater efficiency in the current resource usage.

Usually, Java developers create programs with blocking code. This works fine until there's a performance problem. Then, they add more threads with similar blocking code. But, using more resources can quickly cause conflicts and issues with multiple users.

Additionally, blocking code wastes resources by causing potentially numerous idle threads to wait for data during latency-prone operations like database requests or network calls.

The parallelization approach, although crucial for maximizing hardware potential, can be complex to comprehend and may result in resource wastage due to contention and concurrency issues.

From Imperative to Reactive Programming

There are two types of coding styles:

  • Imperative: This involves a sequential series of tasks, with each task running one at a time, following the completion of the previous task. Data is processed in large quantities and cannot be passed on to the next task until the previous task has finished processing the entire dataset.

  • Reactive: In this coding style, a set of tasks is defined to process data, and these tasks can run concurrently. Each task processes subsets of the data, passing it on to the next task in the sequence while continuing to work on another subset of the data.

Publisher & Subscriber Communication

Step 1: Subscriber seeks to connect

Step 2: The Publisher calls the `onSubscribe` method with an instance of Subscription

What is a Publisher?
A Publisher is a provider of a potentially unbounded number of sequenced elements, publishing them according to the demand received from its Subscriber(s). A Publisher can serve multiple Subscribers subscribed subscribe(Subscriber) dynamically at various points in time.
Behind the scene:
onSubscribe method is invoked after calling Publisher.subscribe(Subscriber). No data will start flowing until Subscription.request(long) is invoked. It is the responsibility of this Subscriber instance to call Subscription.request(long) whenever more data is wanted. The Publisher will send notifications only in response to Subscription.request(long).

Step 3: A Subscription is established between the Subscriber and Publisher.

  • A Subscription represents a one-to-one lifecycle of Subscriber subscribing to a Publisher. It can only be used once by a single Subscriber. It is used to both signal the desire for data and cancel demand (and allow resource cleanup).

  • Subscription has two methods:

public void request(long n);No events will be sent by a Publisher until demand is signaled via this method. It can be called however often and whenever needed—but if the outstanding cumulative demand ever becomes Long.MAX_VALUE or more, it may be treated by the Publisher as "effectively unbounded". Whatever has been requested can be sent by the Publisher so only signal the demand for what can be safely handled. A Publisher can send less than is requested if the stream ends but then must emit either Subscriber.onError(Throwable) or Subscriber.onComplete()
public void cancel();Request the Publisher to stop sending data and clean up resources. Data may still be sent to meet previously signaled demand after calling cancel

Step 4: Publisher pushes data via the onNext method to the Subscriber

What does onNext do?
Data notification sent by the Publisher in response to requests to Subscription.request(long). Params: t – the element signaled

Step 5: Publisher finished supplying elements and calls onComplete()

Successful terminal state. No further events will be sent even if the Subscription.request(long) is invoked again.

Step 6: If there is an error it will send onError(Throwable t)

Did you find this article valuable?

Support Ish Mishra by becoming a sponsor. Any amount is appreciated!