Grasping Reactive Programming in Java: A Basic Guide with Reactor

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).
—
https://en.wikipedia.org/wiki/Reactive_programming
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?
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 ofSubscriber
subscribing to aPublisher
. It can only be used once by a singleSubscriber
. It is used to both signal the desire for data and cancel demand (and allow resource cleanup).Subscription has two methods:
Methods | Description |
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?
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)
Subscribe to my newsletter
Read articles from Ish Mishra directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Ish Mishra
Ish Mishra
Welcome to Bits8Byte! I’m Ish, a seasoned Software Engineer with 11+ years of experience in software development, automation, and AI/ML. I have a deep passion for technology, problem-solving, and continuous learning, and I created this blog to share my insights, experiences, and discoveries in the ever-evolving world of software engineering. Throughout my career, I’ve worked extensively with Java (Spring Boot), Python (FastAPI), AI/ML, Cloud Computing (AWS), DevOps, Docker, Kubernetes, and Test Automation frameworks. My journey has led me to explore microservices architecture, API development, observability (OpenTelemetry, Prometheus), and AI-powered solutions. On this blog, you’ll find practical tutorials, in-depth technical discussions, and real-world problem-solving strategies. I’ll also share my experiences working on high-performance microservices, AI applications, cloud deployments, and automation frameworks, along with best practices to help fellow developers and engineers. I encourage you to join the conversation—leave comments, ask questions, and share your thoughts! Let’s learn, innovate, and grow together in this exciting journey of software development.