Search code examples
javaspringkotlinspring-webfluxproject-reactor

Understanding execution order with .then(Mono{})


I'm trying to understand the behaviour of .then(Mono(x)) in spring webflux.

I have the following code:

fun storeIfValid(x): Mono<Long> =
    doSomeChecksThatMightFail().then(repo.save(x))

Reading the documentation of then lets me believe, that the repo.save(x) block is only executed when the onComplete signal is published from the previous Mono. In case the first Mono fails, I would then expect no interaction with my repository.

I know I could "fix" it by using the following:

fun storeIfValid(x): Mono<Long> =
    doSomeChecksThatMightFail().flatMap{ repo.save(x) }

or also the following works:

fun storeIfValid(x): Mono<Long> =
    doSomeChecksThatMightFail().then(Mono.defer{repo.save(x)})

but I'm struggling to fully understand why. It seems that the Mono in the then-block is already executed before the initial Mono completes?

Maybe someone can shed a bit of light on this topic.. all explanations I found so far (stackoverflow, chatgpt and documentation) were a bit lacklustre.


Solution

  • Arguments passed to a method call are always evaluated before the method is called. repo.save(x) must be evaluated (i.e. invoked) before then is called, so whatever then does will have no effect on when repo.save(x) is called.

    What then does in this case, is to create a new Mono that plays the Mono returned by repo.save(x), after the Mono returned by doSomeChecksThatMightFail() has completed.

    In other words, x is saved immediately, but the Mono returned by storeIfValid will only emit the saved instance (returned by repo.save(x)) after doSomeChecksThatMightFail() has completed.

    In the flatMap and defer cases, repo.save(x) is in a lambda expression. When a lambda expression is evaluated, it just creates an object representing that lambda expression. It is only after the lambda has been invoked, will the code in the lambda expression run.

    For flatMap, the lambda is called when doSomeChecksThatMightFail() emits a value. For defer, the lambda is called when the Mono is played, i.e. after doSomeChecksThatMightFail completes, because of .then(...).


    Note that the flatMap and defer approaches are slightly different. flatMap will not emit a value or run the lambda if doSomeChecksThatMightFail completes without emitting a value.