1. Introduction
In the first article of a series related to the Project Reactor, I would like to show you the process of creating Monos with Mono.just(), Mono.defer(), Mono.fromSupplier() and Mono.create() methods. After this step by step tutorial, you will have a decent understanding of each method and differences between them.
Nonetheless, this is not an introduction to the Project Reactor and to get the most out of this article, you need to understand at least the basic concepts associated with it. Considering that, I highly recommend their official docs, but if you would be interested in a video explanation, please let me know in the comments section below.
As the last thing in the introduction I would like welcome you to the Codersee after a long period of time. Last 12 months have been a pretty tough time for me and I had to suspend all of my blog activities. Nevertheless, I am back to blogging bringing you new portion of knowledge and ideas. I am so excited about upcoming months and really hope that you will be too.
2. Eager vs Lazy Evaluation
Before we start considerations about Mono creation, let’s take a while to understand the difference between eager and lazy evaluation. In general, both terms describe the way expressions are evaluated. With eager evaluation, the value will be evaluated immediately, when the instruction has been encountered. In the second case, the process will be delayed until the value is really needed, hence the term- lazy evaluation.
Make a real progress thanks to practical examples, exercises, and quizzes.
- 64 written lessons
- 62 quizzes with a total of 269 questions
- Dedicated Facebook Support Group
- 30-DAYS Refund Guarantee
3. Hot vs Cold Publishers
Another terms I would like to cover before heading to the clue of this article are hot and cold publishers. In Project Reactor all Publishers (like Monos or Fluxes) are considered either one of them. Cold publishers, generate new data for each subscription (consider it a bit similar to lazy evaluation), whereas hot publishers are independent of subscribers and will produce data anyway (similarity to eagerness).
Undoubtedly, the two above paragraphs are just a brief summary and I highly recommend you do a further investigation. However, I am pretty sure that this knowledge will be sufficient for the purpose of this tutorial.
4. Imports
With that being said, let’s start by importing necessary library to our project:
implementation("io.projectreactor:reactor-core:3.4.12")
Note: you can find the source code for this article, as always, in our GitHub repository.
5. Create Monos
After that, let’s define what exactly a Mono is in terms of Project Reactor? Well, it’s nothing else than just a specialized Publisher capable of asynchronous emitting of either 1 or 0 element. Fluxes, on the other hand, represent an asynchronous sequence of 0 to N elements.
4.1. Mono.just()
Provided that, let’s start everything with an example function called successfulDateFetching:
fun successfulDateFetching(): LocalDateTime { Thread.sleep(500) println("GETTING DATE") return LocalDateTime.now() }
As we can see, it will be responsible for three things:
- causing the currently executing thread to sleep,
- printing the text to the output,
- and finally, returning the current date-time information from the system clock
As the next step, let’s let’s implement the following Mono.just()
example:
fun monoJust(): Mono<LocalDateTime> = Mono.just(successfulDateFetching()) fun monoJustSubscription() { val myMono = monoJust() myMono.subscribe(::println) myMono.subscribe(::println) myMono.subscribe(::println) } fun monoJustInstantiation() { val myMono = monoJust() }
The above code is responsible for creating a new Mono inside the monoJust function and after that it’s utilized inside the monoJustSubscription or monoJustInstantiation methods. Given that, it’s worth to quote the Reactor documentation here:
Nothing Happens Until You subscribe()
And that’s one of the most important things when dealing with Reactor- data do not start pumping into by default. By creating a Mono, we’re just defining an asynchronous process, but all the magic happens when we tie it to a Subscriber. Personally, I would compare that to the process of class definition– just like a class is a simple skeleton with some predefined behavior until we create it’s instance, so here a Mono defines the process of data flow until we subscribe to it.
Note: As always, I highly encourage you to copy and run all of the examples manually, so that you could get the most out of this article.
With that being said, we expect that that a date-time information will be printed 3 times after running monoJustSubscription and nothing happen in second scenario. Let’s validate if it’s true:
// monoJustSubscription GETTING DATE 2021-12-08T07:58:22.053724500 2021-12-08T07:58:22.053724500 2021-12-08T07:58:22.053724500 // monoJustInstantiation GETTING DATE
As you might have noticed- the result seems to be a bit odd. Why are the printed values exactly the same in the first case and why does the GETTING DATE has been even printed without subscription?
Basically, this is the main difference between a Mono.just() and the rest of the methods we’ll compare in this article. It is a hot publisher and the value has been captured at the instantiation time. In fact, the successfulDateFetching() has been invoked when we created and instance of Mono- even if we didn’t subscribe to it! Undeniably, we should be aware of this behavior especially when dealing with multiple subscribers.
4.2. Mono.defer()
Thankfully, Mono.just() is not the only possible way of creating Monos. Let’s see what can we do to delay the process of obtaining the date-time with Mono.defer():
fun monoDefer(): Mono<LocalDateTime> = Mono.defer { monoJust() } fun monoDeferSubscription() { val myMono = monoDefer() myMono.subscribe(::println) myMono.subscribe(::println) myMono.subscribe(::println) } fun monoDeferInstantiation() { val myMono = monoDefer() }
After that, let’s run the examples and see the result:
// monoDeferSubscription GETTING DATE 2021-12-08T09:01:56.457742200 GETTING DATE 2021-12-08T09:01:56.970125600 GETTING DATE 2021-12-08T09:01:57.487761200 // monoDeferInstantiation
This time, we’ve achieved a real laziness- a successfulDateFetching() was triggered each time a new Subscriber was registered. Moreover, nothing happened, when we just instantiated a Mono. The reason behind this is pretty straightforward- Mono.defer() will supply a target Mono (created by the monoJust(), in our case) to each Subscriber. In fact, the delay between each subscription could be extended to whatever value we would like to, and the printed date-time would be up to date each time.
At this point, we can clearly see that these two methods serve different purposes. If we are dealing with some constant data, or data set, or we are just OK with the data being obtained eagerly, then the Mono.just() should be the choice. On the other hand, if we want the subscriber to receive data calculated at the time of subscription, then the Mono.defer() should be picked to “wrap” another Mono.
4.3. Mono.fromSupplier()
Similarly to defer(), we can delay the data evaluation with Mono.fromSupplier() case. As the documentation states, it allows us to:
Create a Mono, producing its value using the provided Supplier. If the Supplier resolves to null, the resulting Mono completes empty.
Given that’s let’s see the following example:
private fun monoFromSupplier(): Mono<LocalDateTime> = Mono.fromSupplier { successfulDateFetching() } fun monoFromSupplierSubscription() { val myMono = monoFromSupplier() myMono.subscribe(::println) myMono.subscribe(::println) myMono.subscribe(::println) } fun monoFromSupplierInstantiation() { val myMono = monoFromSupplier() }
In this case, the output presents as follows:
// monoFromSupplierSubscription GETTING DATE 2021-12-08T08:33:59.168426800 GETTING DATE 2021-12-08T08:33:59.671269400 GETTING DATE 2021-12-08T08:34:00.187244300 // monoFromSupplierInstantiation
We can clearly spot that this, and Mono.defer() execution worked exactly the same. As a word of explanation- both methods serve us to delay (defer) the moment of capturing the value. Most of the time, we ill lean toward them when dealing with external libraries, or another part of the code that we do not have an influence on. To put it simple, our choice will be:
- Mono.defer()– when dealing with another library, method or whatever else returning a Mono instance
- Mono.fromSupplier()– when consuming a simple value being returned from external (not a Mono)
Additionally, it’s worth mentioning that Project Reactor ships with another method called fromCallable(), which you might be interested in, as well (but considerations on Supplier and Callable usage won’t be a part of this article).
4.3. Mono.create()
Lastly, let’s have a look at Mono.create(). Despite it’s simple name it is the most advanced way of creating Monos covered in this article. Again, I highly recommend you to check out the official documentation for more thorough exaplanation and examples.
As a word of clarification- Mono.create() allows us to deal with internal Mono signals through the MonoSink<T> wrapper’s create(), create(T) and error(Throwable) methods. Similarly, it creates a deffered emitter, I believe the result of the following example is just a formality:
fun monoCreate(): Mono = Mono.create { monoSink -> monoSink.success(successfulDateFetching()) } fun monoCreateSubscription() { val myMono = monoCreate() myMono.subscribe(::println) myMono.subscribe(::println) myMono.subscribe(::println) } fun monoCreateInstantiation() { val myMono = monoCreate() } // #Result // monoCreateSubscription // GETTING DATE 2021-12-08T08:33:59.168426800 // GETTING DATE 2021-12-08T08:33:59.671269400 // GETTING DATE 2021-12-08T08:34:00.187244300 // monoCreateInstantiation
As we might have noticed, the instantiation process is deferred here, just like when dealing with defer() and fromSupplier() methods. Nevertheless, it is the most advanced method presented in this tutorial which gives us much more control over emitted values and in most cases- it’s usage won’t be necessary. On the other hand, it might be a great choice when dealing with some callback-based APIs.
5. Conclusion on Mono.just() vs defer() vs fromSupplier() vs create()
Finally, let’s have a quick recap on Mono.just() vs defer() vs fromSupplier() vs create():
- Mono.just()– value captured at the time of instantiation, each Subscriber will receive the same value
- Mono.defer()– supplies a target Mono to each Subscriber, so the value will be obtained when subscribing
- Mono.fromSupplier()– produces a value using provided subscriber on subscribe
- Mono.create()– creates a deferred emitter, the most advanced method allowing to operate on MonoSink<T>
6. Summary
And that’s all for the first article describing differences between Mono’s just(), defer(), fromSupplier() and create() methods. Additionally, in the next episode, we will focus on differences in null and exceptions handling.
Finally, I really hope that after this read you’ll get a better understanding of their behavior and use cases. For the source code, please refer to this GitHub repository. Moreover, if you would like to ask about anything or need some more explanation, please do it in the comment section below, or by using the contact form.