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
Feature | Mono.just() | Mono.defer() | Mono.create() |
---|---|---|---|
Creation Timing | Immediate | Deferred until subscription | Programmatically defined, callback-based |
Use Case | Static value that is known upfront | Dynamic value per subscription, allows computation on subscription | Custom logic that allows emitting a value, error, or completion based on external conditions |
Flexibility | Low, as the value is pre-defined | Medium, allows dynamic values, but the value is computed at subscription time | High, allows full control over value emissions and integration with non-reactive systems |
Complexity | Low, simple and direct | Medium, requires understanding of deferred behavior and timing | High, requires managing callback logic and emitting signals programmatically |
Performance | Best for simple cases with minimal overhead | Efficient for deferring resource-intensive operations until subscription | More overhead due to callback-based handling, but suitable for integrating complex logic |
When to Use | When the value is known ahead of time and needs to be emitted immediately | When the value needs to be computed or fetched at the time of subscription | When you need to integrate external or legacy systems, or have complex logic for emitting values |
Thread Safety | Thread-safe, value is already available | Thread-safe, computation is deferred until subscription | Thread-safe, but care must be taken to handle synchronization within the callback |
Error Handling | Cannot easily handle errors unless they are pre-known | Can handle errors dynamically when the Mono is created during subscription | Allows 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.