Codersee

Mono.just() vs defer() vs fromSupplier() vs create() – Part 1

A featured image for category: Uncategorized

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.

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.

Leave a Reply

Your email address will not be published.

Categories

Author

Piotr Wolak

Piotr Wolak

Founder Of Codersee

Join Newsletter And Get 2 FREE EBOOKS

Image shows the covers of free ebooks accessible for newsletter subscribers.

Join the FREE weekly newsletter and get two free eBooks:

Image shows the covers of free ebooks accessible for newsletter subscribers.

You may opt out any time. Terms of Use and Privacy Policy.

To make Codersee work, we log user data. By using our site, you agree to our Privacy Policy and Terms of Use.