Just as a windmill relies on its bearings and sails to harness energy, every reactive framework is built on core components that work together to process data. This analogy is illustrative: while a windmill’s parts are physical, the components of a reactive framework are abstract concepts that collaborate to move and transform data. To effectively chain these building blocks, we must first understand their roles and interactions.
In JavaRx and Project Reactor, the fundamental interfaces are Publisher, Subscriber, Subscription, and Processor. Each plays a unique and crucial part in the reactive ecosystem.
At the top of the chain is the publisher, responsible for producing and emitting data. In Project Reactor, Publisher is an interface, with Mono (for a single value) and Flux (for multiple values) as its main implementations. The Publisher defines a contract with a single method—subscribe—which allows subscribers to register for data. Once subscribed, the publisher creates a new Subscription representing the contract between a given subscriber and itself. The publisher is then responsible for delivering data items to the consumer. Upon successful completion, it emits a completion signal; if an error occurs, it emits an error signal.
The subscriber receives data from the publisher. In the context of Project Reactor, it is also an interface with defined methods: onSubscribe(), onNext(), onError(), and onComplete(). The onSubscribe() method is called by the publisher immediately after creating a new Subscription, allowing the subscriber to initiate data requests. With onNext(), the subscriber processes each received item. When an error signal is received, onError() is triggered; onComplete() is triggered after the completion signal. After onError() or onComplete(), no further communication occurs between the publisher and its subscriber.
The subscription defines a one-to-one contract between a publisher and a single subscriber. The subscriber initiates the flow by subscribing to the publisher, which then instantiates the subscription object and passes it to the subscriber via onSubscribe(). Importantly, publishers only emit data when it is requested via the Subscription, enabling backpressure and flow control. In practice, frameworks often abstract away the subscription; for example, Project Reactor’s BaseSubscriber allows you to use the request() method directly on the Subscriber.
Finally, the Processor acts as a middle man. Whenever we chain together multiple reactive components, those in the middle must act as both publishers and subscribers. The Processor interface combines both roles, allowing it to receive data, process or transform it, and then emit it downstream. In Project Reactor, processors are often implemented as chains of operators (like filter and map), but there is also a Processor interface for custom implementations.
Here we give an example of a reactive chain that uses one publisher, one processor, and one subscriber. This example provides a clearer overview of how these roles interact in practice.
// Publisher: emits numbers 1 to 10
Flux<Integer> publisher = Flux.range(1, 10);
// Processor: filters even numbers and squares them
Flux<Integer> processor = publisher
.filter(n -> n % 2 == 0)
.map(n -> n * n);
// Subscriber: prints each processed item
processor.subscribe(
item -> System.out.println("Processed: " + item),
error -> System.err.println("Error: " + error),
() -> System.out.println("Processing complete.")
);
In this post, we have given an overview of the main components of the reactive chain. We discussed the Publisher—the ultimate source of data in the reactive stream—the Processor as the middle component that can transform or filter data, and the Subscriber at the end of the chain. We also briefly mentioned the main implementations of Publisher: Mono and Flux. Finally, we covered the contract between Publisher and Subscriber—the Subscription—which plays a crucial role in managing backpressure and flow control in reactive systems.
