Reactive Programming in Java: Reactor Library

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

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                    

Recommendations