Spring reactive Mono.fromCallable vs Mono.justOrEmpty
In the Spring Reactive Framework, Mono
is a crucial part of handling asynchronous and non-blocking streams. Two commonly used methods in the Mono
class are Mono.fromCallable
and Mono.justOrEmpty
. Let us delve into understanding Spring Reactive and explore the differences between Mono fromCallable vs justOrEmpty, two key methods in reactive programming.
1. Introduction to Mono
Mono
is a reactive type in the Project Reactor library that can emit either a single value or an error signal. It’s typically used for operations that result in zero or one item. Mono
is ideal for representing asynchronous computations that produce a single result, such as retrieving data from a database, reading a file, or calling an external service. Unlike Flux
, which can emit multiple items, Mono
is used when you expect only one result or none at all. This makes Mono
a powerful tool for modeling operations that either succeed with a value or fail with an error. By using Mono
, developers can compose asynchronous, non-blocking flows in a declarative manner, allowing for better resource utilization and enhanced scalability in applications. Additionally, Mono
provides a variety of operators to transform, combine, and handle errors in a reactive way, further promoting efficient error management and flow control in modern applications. For Project Reactor include the following dependency in the code:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> </dependency>
1.1 Mono.fromCallable
Mono.fromCallable
is a method that creates a Mono
from a Callable
. It lazily evaluates the given Callable
and emits its result when subscribed to. This means that the Callable
is not executed until a subscriber requests the data, ensuring that the computation or resource-intensive operation is only performed when needed. This is particularly useful in scenarios where you want to defer execution of a potentially blocking task, such as database queries, file I/O, or network calls, to avoid unnecessary resource consumption until the data is actually required. Additionally, Mono.fromCallable
can handle exceptions thrown by the Callable
, propagating them as error signals in the reactive stream, allowing for robust error handling strategies in reactive pipelines.
Let’s illustrate this with an example to gain a better understanding.
import reactor.core.publisher.Mono; import java.util.concurrent.Callable; public class FromCallableExample { public static void main(String[] args) { Callable<String> callable = () -> { // Simulate a long-running task Thread.sleep(1000); return "Result from Callable"; }; Mono<String> mono = Mono.fromCallable(callable); mono.subscribe(System.out::println, Throwable::printStackTrace); } }
1.1.1 Code Explanation
The given code demonstrates the usage of Mono.fromCallable
to create a Mono
that is wrapped around a Callable
operation. In this case, a simple Callable
is created using a lambda expression. The Callable
simulates a long-running task by sleeping for 1000 milliseconds (1 second), which mimics a time-consuming operation such as a database query or a file I/O operation. After the sleep, it returns a string value: “Result from Callable”.
Next, the Mono.fromCallable
method is used to create a Mono
that wraps the Callable
. The Mono
will lazily execute the Callable
only when a subscriber subscribes to it, meaning the long-running task won’t begin until the Mono
is subscribed to.
The subscribe
method is called on the Mono
to trigger the execution of the Callable
. The subscribe
method takes three parameters: the first is a consumer that processes the emitted value (in this case, printing the result to the console), the second is a consumer that handles any potential errors (here, the stack trace of any error is printed), and the third is a consumer for completion handling (which is omitted in this example). When the Mono
emits the result, it prints “Result from Callable” to the console.
Since the Mono.fromCallable
method is used, the task is executed asynchronously and lazily, which is an important concept in reactive programming to avoid unnecessary blocking operations. This ensures that the task is only performed when it’s required, making the application more efficient and responsive.
1.1.2 Code Output
The code produces the following output:
Result from Callable
1.2 Mono.justOrEmpty
Mono.justOrEmpty
is a method that creates a Mono
from a potentially null value. If the value is non-null, it emits the value; otherwise, it completes without emitting any value. This makes Mono.justOrEmpty
particularly useful for handling optional values or situations where you may not have data to return. Unlike Mono.just
, which throws a NullPointerException
when given a null
value, Mono.justOrEmpty
handles null
gracefully by simply completing without emitting any result. This provides a cleaner and more intuitive way to handle cases where a value might be absent.
In reactive programming, handling optional or missing data efficiently is crucial, and Mono.justOrEmpty
simplifies this process by allowing you to avoid the need for explicit null checks. If the value is present, it’s emitted as a result of the Mono
, and if it’s not, the Mono
completes without emitting anything. This behavior allows you to easily chain other operators and reactively handle cases of missing data. It’s particularly useful when dealing with optional values, such as database results, configuration values, or responses from external APIs, where the absence of a value should not lead to an error but rather to an empty completion of the Mono
.
Using Mono.justOrEmpty
can help maintain clean and readable code by reducing the need for conditional checks or null safety logic, allowing developers to focus on composing reactive flows without worrying about null values explicitly. The method aligns with the principles of reactive programming by providing an elegant way to deal with the absence of data in a non-blocking and declarative manner.
Let’s illustrate this with an example to gain a better understanding.
import reactor.core.publisher.Mono; public class JustOrEmptyExample { public static void main(String[] args) { String nonNullValue = "Hello, Mono!"; String nullValue = null; Mono<String> nonNullMono = Mono.justOrEmpty(nonNullValue); Mono<String> nullMono = Mono.justOrEmpty(nullValue); nonNullMono.subscribe(System.out::println, Throwable::printStackTrace, () -> System.out.println("Completed non-null Mono")); nullMono.subscribe(System.out::println, Throwable::printStackTrace, () -> System.out.println("Completed null Mono")); } }
1.2.1 Code Explanation
The provided code demonstrates the usage of the Mono.justOrEmpty
method in the context of handling potentially null values. The code starts by declaring two variables: nonNullValue
, which holds the string “Hello, Mono!”, and nullValue
, which is explicitly set to null
.
Next, the code creates two Mono
objects using Mono.justOrEmpty
. The first, nonNullMono
, is created from the non-null value nonNullValue
, and the second, nullMono
, is created from the null
value nullValue
. The Mono.justOrEmpty
method emits the value if it is non-null or completes without emitting anything if the value is null
. In this case, nonNullMono
will emit the string “Hello, Mono!” and nullMono
will simply complete without emitting anything.
Both Mono
instances are then subscribed to, triggering the respective operations. For nonNullMono
, the subscribe
method has three parameters: the first one prints the emitted value (which will be “Hello, Mono!”), the second handles errors (printing any stack trace in case of an exception), and the third handles the completion signal, printing “Completed non-null Mono” when the Mono
completes successfully. For nullMono
, it completes without emitting any value, and the completion handler prints “Completed null Mono”.
This example illustrates the behavior of Mono.justOrEmpty
when handling optional or nullable data. It ensures that Mono
emits a value only if it is present, and if the value is absent (i.e., null
), it simply completes without causing any errors. This behavior is helpful in reactive programming when dealing with optional data, allowing for cleaner and more predictable data flows without explicitly checking for null
.
1.2.2 Code Output
The code produces the following output:
Hello, Mono! Completed non-null Mono Completed null Mono
3. When to Use fromCallable?
Mono.fromCallable
should be used when you have a potentially blocking or time-consuming operation that should be executed asynchronously. This method ensures that the operation is only invoked when a subscriber subscribes to the Mono
, meaning that the execution is deferred until necessary. This is particularly useful for operations such as database queries, web service calls, or file I/O, which may involve waiting for external resources or processing large amounts of data. By deferring execution, Mono.fromCallable
helps improve application performance by avoiding unnecessary operations or resource consumption when the result is not needed immediately.
In addition to deferring execution, Mono.fromCallable
also makes sure that the execution is non-blocking, as it allows for asynchronous operations. In a reactive application, blocking operations can be detrimental as they tie up threads and reduce scalability. By using Mono.fromCallable
, the potentially blocking operation runs asynchronously, freeing up threads for other tasks while waiting for the result. Once the operation completes, the result is emitted by the Mono
, allowing the rest of the reactive stream to continue processing.
This method is ideal when you need to wrap blocking or expensive operations into a reactive flow. It enables you to integrate traditional blocking APIs into reactive systems without disrupting the flow of other reactive operations. Additionally, since the Mono
is lazy, the operation will only execute when it’s actually needed, reducing unnecessary computation and providing more efficient resource management in reactive applications.
Furthermore, Mono.fromCallable
handles exceptions in a reactive manner. If the Callable
throws an exception, the Mono
will propagate the error to the subscriber, making error handling consistent with other parts of the reactive stream. This ensures that reactive error handling strategies can be applied uniformly, whether you’re working with blocking or non-blocking operations.
4. Conclusion
Both Mono.fromCallable
and Mono.justOrEmpty
serve distinct purposes in the reactive programming model. Use Mono.fromCallable
for deferred execution of operations and Mono.justOrEmpty
for handling potentially null values in a reactive stream. Understanding their use cases helps in building efficient and responsive reactive applications.