Reactive Programming in Java with Project Reactor
Reactive programming is a development paradigm focused on asynchronous data streams and the propagation of changes. In Java, this concept is implemented through the Reactive Streams specification and its most popular implementation — the Project Reactor library (part of the Spring WebFlux ecosystem).
Unlike traditional imperative code, where a program executes commands sequentially, the reactive approach allows processing data as it arrives without blocking execution threads. This is especially important for high-load systems, microservices, and applications with a large number of concurrent requests.
Core Components of Reactor
The library provides two main types of reactive sequences:
- Mono<T> — represents a stream that can contain 0 or 1 element. Used for single asynchronous operations (e.g., an HTTP request, fetching a record from a database).
- Flux<T> — represents a stream that can contain from 0 to N elements. Suitable for collections, event streams, or data arriving in parts.
Both types implement the Publisher interface from the Reactive Streams specification and support the backpressure mechanism — the ability for a subscriber to control the rate of data reception.
Example: Creating and Processing a Flux
Let's look at a simple example of creating a reactive stream of numbers and processing it:
import reactor.core.publisher.Flux;
public class ReactorExample { public static void main(String[] args) { Flux<Integer> numbers = Flux.just(1, 2, 3, 4, 5);
numbers .map(n -> n * 2) // transformation .filter(n -> n % 3 == 0) // filtering .subscribe( data -> System.out.println("Received: " + data), error -> System.err.println("Error: " + error), () -> System.out.println("Completed!") ); }}In this example:
Flux.just()creates a stream of five numbers.map()multiplies each element by 2.filter()keeps only numbers divisible by 3.subscribe()starts processing — prints the result, handles errors, and signals completion.
Asynchrony and Working with Mono
Reactor allows performing asynchronous operations without blocking threads. For example, simulating a request to a remote service:
import reactor.core.publisher.Mono;import java.time.Duration;
public class AsyncExample { public static void main(String[] args) throws InterruptedException { Mono<String> asyncResult = Mono.fromSupplier(() -> { try { Thread.sleep(1000); // simulate delay } catch (InterruptedException e) { e.printStackTrace(); } return "Data received"; }).subscribeOn(Schedulers.boundedElastic()); // execute in another thread
System.out.println("Request sent..."); asyncResult.subscribe(System.out::println); Thread.sleep(2000); // wait for completion }}The key point: calling .subscribeOn() moves the execution of the expensive operation to a separate thread without blocking the main one. The subscriber will receive the result asynchronously.
Error Handling in Reactive Streams
Unlike try-catch, in reactive programming errors are handled through operators:
Flux<String> fluxWithError = Flux.just("A", "B", "C") .map(s -> { if ("B".equals(s)) t Also in library
Caching in C++: Redis, Data Layer, and Other Tools
Machine Learning in Kotlin: Overview of Libraries for Data Science and ML
Java for Geolocation and Maps: Libraries, APIs, and Examples
HTTP Client for C# (.NET): Top Libraries for Working with Web Requests