1. Overview

In Reactive Programming, there are many ways we can create a publisher of type Mono or Flux. Here, we'll look at the use of the defer method to delay the execution of a Mono publisher.

2. What Is the Mono.defer Method?

We can create a cold publisher which can produce at most one value using defer method of the Mono. Let's look at the method signature:

public static <T> Mono<T> defer(Supplier<? extends Mono<? extends T>> supplier)

Here, defer takes in a Supplier of Mono publisher and returns that Mono lazily when subscribed downstream.

However, the question is, what is a cold publisher or a lazy publisher? Let's look into that.

Executing thread evaluates cold publishers only when consumers subscribe to them. While the hot publisher evaluated eagerly before any subscription. We have the method Mono.just() that gives a hot publisher of type Mono.

3. How Does It Work?

Let's explore a sample use case having Supplier of type Mono:

private Mono<String> sampleMsg(String str) {
    log.debug("Call to Retrieve Sample Message!! --> {} at: {}", str, System.currentTimeMillis());
    return Mono.just(str);
}

Here, this method returns a hot Mono publisher. Let's subscribe to this eagerly:

public void whenUsingMonoJust_thenEagerEvaluation() throws InterruptedException {

    Mono<String> msg = sampleMsg("Eager Publisher");

    log.debug("Intermediate Test Message....");

    StepVerifier.create(msg)
      .expectNext("Eager Publisher")
      .verifyComplete();

    Thread.sleep(5000);

    StepVerifier.create(msg)
      .expectNext("Eager Publisher")
      .verifyComplete();
}

On execution, we can see the following in logs:

20:44:30.250 [main] DEBUG com.baeldung.mono.MonoUnitTest - Call to Retrieve Sample Message!! --> Eager Publisher at: 1622819670247
20:44:30.365 [main] DEBUG reactor.util.Loggers$LoggerFactory - Using Slf4j logging framework
20:44:30.365 [main] DEBUG com.baeldung.mono.MonoUnitTest - Intermediate Test Message....

Here, we can notice that:

  • According to the instructions sequence, the main thread eagerly executes the method sampleMsg.
  • On both subscriptions using StepVerifier, the main thread uses the same output of sampleMsg. Therefore, no new evaluation.

Let's see how Mono.defer() converts it to a cold (lazy) publisher:

public void whenUsingMonoDefer_thenLazyEvaluation() throws InterruptedException {

    Mono<String> deferMsg = Mono.defer(() -> sampleMsg("Lazy Publisher"));

    log.debug("Intermediate Test Message....");

    StepVerifier.create(deferMsg)
      .expectNext("Lazy Publisher")
      .verifyComplete();

    Thread.sleep(5000);

    StepVerifier.create(deferMsg)
      .expectNext("Lazy Publisher")
      .verifyComplete();

}

On executing this method, we can see the following logs in the console:

20:01:05.149 [main] DEBUG com.baeldung.mono.MonoUnitTest - Intermediate Test Message....
20:01:05.187 [main] DEBUG com.baeldung.mono.MonoUnitTest - Call to Retrieve Sample Message!! --> Lazy Publisher at: 1622817065187
20:01:10.197 [main] DEBUG com.baeldung.mono.MonoUnitTest - Call to Retrieve Sample Message!! --> Lazy Publisher at: 1622817070197

Here, we can notice few points from the log sequence:

  • StepVerifier executes the method sampleMsg on each subscription, instead of when we defined it.
  • After a delay of 5 seconds, the second consumer subscribing to the method sampleMsg executes it again.

This is how the defer method turns hot into a cold publisher.

4. Use Cases for Mono.defer?

Let's look at the possible use cases where we can use Mono.defer() method:

  • When we have to conditionally subscribe to a publisher
  • When each subscribed execution could produce a different result
  • deferContextual can be used for the current context-based evaluation of publisher

4.1. Sample Usage

Let's go through one sample that is using the conditional Mono.defer() method:

public void whenEmptyList_thenMonoDeferExecuted() {

    Mono<List<String>> emptyList = Mono.defer(() -> monoOfEmptyList());

    //Empty list, hence Mono publisher in switchIfEmpty executed after condition evaluation
    Flux<String> emptyListElements = emptyList.flatMapIterable(l -> l)
      .switchIfEmpty(Mono.defer(() -> sampleMsg("EmptyList")))
      .log();

    StepVerifier.create(emptyListElements)
      .expectNext("EmptyList")
      .verifyComplete();
}

Here, the supplier of Mono publisher sampleMsg is placed in switchIfEmpty method for conditional execution. Hence, sampleMsg executed only when it is subscribed lazily.

Now, let's look at the same code for the non-empty list:

public void whenNonEmptyList_thenMonoDeferNotExecuted() {

    Mono<List<String>> nonEmptyist = Mono.defer(() -> monoOfList());

    //Non empty list, hence Mono publisher in switchIfEmpty won't evaluated.
    Flux<String> listElements = nonEmptyist.flatMapIterable(l -> l)
      .switchIfEmpty(Mono.defer(() -> sampleMsg("NonEmptyList")))
      .log();

    StepVerifier.create(listElements)
      .expectNext("one", "two", "three", "four")
      .verifyComplete();
}

Here, the sampleMsg isn't executed because it isn't subscribed.

5. Conclusion

In this article, we discussed Mono.defer() method and hot/cold publishers. In addition, how we can convert a hot publisher to a cold publisher. Finally, we also discussed its working with sample use cases.

As always, the code example is available over on GitHub.

Generic bottom

Get started with Spring 5 and Spring Boot 2, through the Learn Spring course:

>> CHECK OUT THE COURSE
Generic footer banner
guest
0 Comments
Inline Feedbacks
View all comments