Enterprise Java

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.

Yatin Batra

An experience full-stack engineer well versed with Core Java, Spring/Springboot, MVC, Security, AOP, Frontend (Angular & React), and cloud technologies (such as AWS, GCP, Jenkins, Docker, K8).
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button