35

Could someone help me to understand the difference between:

  • Mono.defer()
  • Mono.create()
  • Mono.just()

How to use it properly?

Honza Zidek
  • 9,204
  • 4
  • 72
  • 118
Anton Tb
  • 417
  • 1
  • 5
  • 8

2 Answers2

54

Mono.just(value) is the most primitive - once you have a value you can wrap it into a Mono and subscribers down the line will get it.

Mono.defer(monoSupplier) lets you provide the whole expression that supplies the resulting Mono instance. The evaluation of this expression is deferred until somebody subscribes. Inside of this expression you can additionally use control structures like Mono.error(throwable) to signal an error condition (you cannot do this with Mono.just).

Mono.create(monoSinkConsumer) is the most advanced method that gives you the full control over the emitted values. Instead of the need to return Mono instance from the callback (as in Mono.defer), you get control over the MonoSink<T> that lets you emit values through MonoSink.success(), MonoSink.success(value), MonoSink.error(throwable) methods. Reactor documentation contains a few good examples of possible Mono.create use cases: link to doc.

The general advice is to use the least powerful abstraction to do the job: Mono.just -> Mono.defer -> Mono.create.

Ilya Zinkovich
  • 4,082
  • 4
  • 25
  • 43
  • Can I say Mono.defer and Mono.create will be executed if there is subscriber? So what about Mono.just? I still don't know when to use it. When I try mono.map(result -> methodA()).switchIfEmpty(Mono.just()), this code will run the mono.just first before map. Actually I still think that switchIfEmpty will be executed after map, because it needs to check that if map return empty then go to switchIfEmpty. But when I try mono.map(result -> methodA()).switchIfEmpty(Mono.create(...)) it will get the desired result flow – Anton Tb May 14 '19 at 01:32
  • 2
    You’re correct. The effect of laziness (deferred evaluation) is achieved in Mono.defer and Mono.create through the use of Supplier and Consumer. You don’t pass the actual result to them, instead you pass an expression that once evaluated later gives an expected result. Mono.just is generally used once you already have a computed value or you expect the value to be computed eagerly, immediately when you construct the reactive pipeline. – Ilya Zinkovich May 14 '19 at 07:31
  • 1
    In your first example it’s expected that whatever you put in Mono.just, will be calculated before the expression that’s inside of map (which can even never be evaluated if no one subscribes). Mono.create and Mono.deferred will behave as you described. – Ilya Zinkovich May 14 '19 at 07:31
4

Although in general I agree with (and praise) @IlyaZinkovich's answer, I would be careful with the advice

The general advice is to use the least powerful abstraction to do the job: Mono.just -> Mono.defer -> Mono.create.

In the reactive approach, especially if we are beginners, it's very easy to overlook which the "least powerful abstraction" actually is. I am not saying anything else than @IlyaZinkovich, just depicting one detailed aspect.

Here is one specific use case where the more powerful abstraction Mono.defer() is preferable over Mono.just() but which might not be visible at the first glance.

See also:

We use switchIfEmpty() as a subscription-time branching:

// First ask provider1
provider1.provide1(someData)
    // If provider1 did not provide the result, ask the fallback provider provider2
    .switchIfEmpty(provider2.provide2(someData))

public Mono<MyResponse> provide2(MyRequest someData) {
    // The Mono assembly is needed only in some corner cases
    // but in fact it is always happening
    return Mono.just(someData)
        // expensive data processing which might even fail in the assemble time
        .map(...)
        .map(...)
        ...
}

provider2.provide2() accepts someData only when provider1.provide1() does not return any result, and/or the method assembly of the Mono returned by provider2.provide2() is expensive and even fails when called on wrong data.

It this case defer() is preferable, even if it might not be obvious at the first glance:

provider1.provide1(someData)
    // ONLY IF provider1 did not provide the result, assemble another Mono with provider2.provide()
    .switchIfEmpty(Mono.defer(() -> provider2.provide2(someData)))
Honza Zidek
  • 9,204
  • 4
  • 72
  • 118