Enterprise Java

Reactive Mono just(), defer(), create() Example

Reactive Programming is a programming paradigm that allows developers to build asynchronous, non-blocking, and event-driven applications. In the Project Reactor library, Mono represents a single asynchronous value (either present or empty). Let us delve into understanding how reactive Mono works, and how we can use methods like just(), defer() and create() to manage asynchronous data flows.

1. Mono.just()

The Mono.just() method is used to create a Mono that emits a specific value. It is straightforward and emits the value immediately upon subscription. This method is particularly useful when you have a known value or a constant that you want to expose as a reactive stream. When you use Mono.just(), the provided value is stored and emitted to all subscribers. It’s important to note that this method does not execute any lazy or deferred logic; the value is prepared and ready to be emitted as soon as a subscriber subscribes to the Mono. This makes it efficient for static or precomputed values, but less suitable for values that need to be computed or fetched on demand.

1.1 Example

Here is an example demonstrating the use of the Mono.just() method:

import reactor.core.publisher.Mono;

public class MonoJustExample {
    public static void main(String[] args) {
        Mono<String> monoJust = Mono.just("Hello, World!");
        monoJust.subscribe(System.out::println);
    }
}

In this example, Mono.just("Hello, World!") creates a Mono that emits the string “Hello, World!”. When the subscribe() method is called, the value is printed to the console.

Hello, World!

1.2 Use cases

The method serves the following purposes:

  • Returning a static configuration value in a reactive service.
  • Emitting a predefined result from a test or demo application.
  • Wrapping a constant value in a reactive stream.

1.3 Limitations

Regardless of its purposes, the method also comes with certain limitations.

  • Not suitable for scenarios where the value needs to be computed at the time of subscription.
  • Does not support lazy evaluation or dynamic value generation.

2. Mono.defer()

Mono.defer() creates a new Mono for each subscriber by using a supplier. It allows for deferring the creation of the Mono until the point of subscription. This means that each time a subscriber subscribes, the supplier function is executed, and a new Mono instance is created and returned. This deferred behavior is particularly useful when the value to be emitted by the Mono needs to be dynamically generated or is dependent on some state or condition that could change over time. Unlike Mono.just(), which emits a pre-existing value, Mono.defer() ensures that the value is freshly computed for each subscriber.

For example, if you need to fetch a value from a database or an external service, and you want to make sure that each subscriber gets the most up-to-date value, using Mono.defer() would be appropriate. It guarantees that the computation or fetching of the value only happens when a subscription is made, not before.

2.1 Example

Here is an example demonstrating the use of the Mono.defer() method:

import reactor.core.publisher.Mono;

public class MonoDeferExample {
    public static void main(String[] args) {
        Mono<String> monoDefer = Mono.defer(() -> Mono.just("Deferred Hello"));
        monoDefer.subscribe(System.out::println);
    }
}

In this example, Mono.defer() is used to create a new Mono instance for each subscription. Each time a subscriber subscribes to monoDefer, the supplier function is executed, and “Deferred Hello” is emitted.

Deferred Hello

2.2 Use cases

The method serves the following purposes:

  • Fetching a value from an external service or database that could change over time.
  • Creating a Mono based on dynamic input or state at the time of subscription.
  • Ensuring that the computation or side effect only occurs when needed.
  • Supports lazy evaluation, ensuring that resources are not wasted on unnecessary computations.
  • Provides flexibility by allowing a fresh computation for each subscriber.

2.3 Limitations

Regardless of its purposes, the method also comes with certain limitations.

  • Introduces a slight overhead due to the creation of a new Mono instance for each subscription.
  • Not suitable for scenarios where a constant value is sufficient and known ahead of time.

3. Mono.create()

Mono.create() allows for creating a Mono by emitting a value or an error programmatically using a callback. This method gives you full control over the emission of the signal, which can be either a success signal with a value or an error signal. When using Mono.create(), you can perform complex or custom operations within the callback provided to the method. The callback receives a MonoSink instance, which you can use to emit a single value, an error, or to indicate that no value will be emitted (completion without value).

This method is useful when you need to integrate non-reactive code or asynchronous APIs into a reactive flow. For example, if you have a callback-based API or need to wrap an external resource that uses event listeners, Mono.create() allows you to bridge that code into the reactive world.

3.1 Example

Here is an example demonstrating the use of the Mono.create() method:

import reactor.core.publisher.Mono;

public class MonoCreateExample {
    public static void main(String[] args) {
        Mono<String> monoCreate = Mono.create(sink -> {
            // Simulate some logic or asynchronous operation
            String result = "Created Hello";
            sink.success(result);
        });
        monoCreate.subscribe(System.out::println);
    }
}

In this example, Mono.create() is used to programmatically emit the value “Created Hello”. The sink.success(result) call inside the callback emits the value to the subscriber.

Created Hello

3.2 Use cases

The method serves the following purposes:

  • Integrating legacy or non-reactive APIs into a reactive pipeline.
  • Handling complex or asynchronous logic before emitting a value.
  • Emitting a value based on external events or conditions.
  • Provides full control over the emission of signals, including success, error, and completion.
  • Allows integration of non-reactive or event-driven APIs with the reactive streams model.

3.3 Limitations

Regardless of its purposes, the method also comes with certain limitations.

  • Can introduce complexity due to the need for manual control over signal emission.
  • Requires careful handling to avoid memory leaks or missed signals.

4. Key Differences

FeatureMono.just()Mono.defer()Mono.create()
Creation TimingImmediateDeferred until subscriptionProgrammatically defined, callback-based
Use CaseStatic value that is known upfrontDynamic value per subscription, allows computation on subscriptionCustom logic that allows emitting a value, error, or completion based on external conditions
FlexibilityLow, as the value is pre-definedMedium, allows dynamic values, but the value is computed at subscription timeHigh, allows full control over value emissions and integration with non-reactive systems
ComplexityLow, simple and directMedium, requires understanding of deferred behavior and timingHigh, requires managing callback logic and emitting signals programmatically
PerformanceBest for simple cases with minimal overheadEfficient for deferring resource-intensive operations until subscriptionMore overhead due to callback-based handling, but suitable for integrating complex logic
When to UseWhen the value is known ahead of time and needs to be emitted immediatelyWhen the value needs to be computed or fetched at the time of subscriptionWhen you need to integrate external or legacy systems, or have complex logic for emitting values
Thread SafetyThread-safe, value is already availableThread-safe, computation is deferred until subscriptionThread-safe, but care must be taken to handle synchronization within the callback
Error HandlingCannot easily handle errors unless they are pre-knownCan handle errors dynamically when the Mono is created during subscriptionAllows for explicit error handling within the callback

5. Conclusion

Understanding when to use Mono.just(), defer(), and create() is crucial in Reactive Programming. just() is best for static values, defer() for dynamic values, and create() for custom logic with more control over the emission of values. Choosing the right method can help build efficient and responsive 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