Invoking non-blocking operations sequentially while consuming from a Flux including retries
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
In reactive programming, handling streams of data efficiently and dealing with potential errors robustly are common challenges. One library that excels in this area is Project Reactor, which provides the Flux and Mono types to handle asynchronous sequences of events. This article focuses on how to invoke non-blocking operations sequentially on data items emitted by a Flux, including handling retries when operations fail.
Understanding Flux
Flux is a reactive type in Project Reactor that represents a stream of 0 to N elements. The operations on Flux are non-blocking and support backpressure, allowing it to only consume resources as needed. For example, consider a Flux<String> which represents a stream of data items that could be retrieved from a database or an external service.
Non-blocking Sequential Operations
When you need to perform operations on each item emitted by a Flux, sequentially and without blocking, you typically use the flatMap operator. This operator applies a function to each item which returns a Mono or another Flux, and then flattens the resulting streams into a single Flux.
Here, performOperation is a method that performs some asynchronous operation (e.g., an HTTP request) and returns a Mono<Result>.
Handling Errors and Retries
A common requirement is robust error handling, particularly retrying failed operations. Project Reactor provides several options for handling retries, which can be configured to attempt retries a certain number of times, or until a specific condition is met.
The retry function can be used when you want to retry an operation immediately for a number of times.
For more complex retry logic, such as delaying retries or applying conditional logic, retryWhen can be used. This method takes a companion Flux that indicates when and how retries should occur.
This example uses exponential backoff with a maximum of three retries and a starting backoff duration of 1 second.
Ensuring Sequential Invocation
Even though flatMap by default merges data concurrently, we can ensure that operations are executed sequentially by limiting concurrency:
The second argument to flatMap specifies the concurrency level. Setting it to 1 ensures that operations are performed sequentially.
Summary Table
| Function | Description | Example |
flatMap | Applies a function to each item and flattens the result | data.flatMap(item -> Mono.just(item.toUpperCase())) |
retry | Retries the stream when an error occurs, a specified number of times | results.retry(3) |
retryWhen | Configures complex retry logic using a companion publisher | results.retryWhen(Retry.backoff(3, Duration.ofSeconds(1))) |
Use Cases and Further Considerations
- Handling Timeouts: Combine
retrywith a timeout for each attempt. This prevents the system from hanging indefinitely if the external service is unavailable. - Combining Results: After processing, you might want to combine results, using
collectList()or another collector, to aggregate all results into a singleMono.
Conclusion
Using Flux to perform non-blocking, sequential operations with retries provides a powerful way to handle reactive streams in Java. This approach is not only efficient but also robust, due to its fine-grained error handling and retry mechanisms. Understanding and utilizing these patterns will greatly enhance the resilience and responsiveness of your reactive applications.

