Reactive Programming
Mono
Error Handling
Java
Spring WebFlux

How to perform an action only if the Mono is empty and throw an error if not empty

Data Structures & Algorithms practice on Codemia

Step through 300+ coding interview problems with animated visualisers that show the data structure changing as the code runs. The Blind 75 are free.

Practice algorithms

Introduction

If a Mono contains a value, you want the pipeline to fail. If it is empty, you want to run some alternate action. In Reactor, the clean pattern is to turn the present value into Mono.error(...) and use switchIfEmpty(...) for the empty path.

The Core Reactor Idea

A Mono<T> has three possible outcomes:

  • it emits one value and completes
  • it completes empty
  • it terminates with an error

Your requirement maps cleanly to those signals:

  • non-empty means "this is forbidden, throw an error"
  • empty means "run the fallback action"

That is why flatMap and switchIfEmpty are the two operators you usually need.

A Direct Solution

Suppose you query a repository and want to create a new record only when nothing already exists.

java
1import reactor.core.publisher.Mono;
2
3public class Example {
4
5    Mono<String> createIfMissing(Mono<String> source) {
6        return source
7            .flatMap(value -> Mono.error(new IllegalStateException(
8                "Expected empty Mono, but found value: " + value
9            )))
10            .switchIfEmpty(Mono.defer(() -> performAction()));
11    }
12
13    Mono<String> performAction() {
14        return Mono.just("created");
15    }
16}

How this works:

  • if source emits a value, flatMap converts that value into an error signal
  • if source is empty, flatMap has nothing to process and the pipeline stays empty
  • 'switchIfEmpty then runs performAction() only in the empty case'

Mono.defer(...) is important when the fallback should be lazy. Without it, the fallback Mono might be assembled too early or capture side effects in ways you did not intend.

A More Realistic Example

Here is a repository-style example that returns Mono<Void>:

java
1import reactor.core.publisher.Mono;
2
3class UserService {
4
5    private final UserRepository repository = new InMemoryUserRepository();
6
7    Mono<Void> createUserIfAbsent(String email) {
8        return repository.findByEmail(email)
9            .flatMap(existing -> Mono.error(new IllegalStateException(
10                "User already exists"
11            )))
12            .switchIfEmpty(Mono.defer(() -> repository.insert(email)));
13    }
14}
15
16interface UserRepository {
17    Mono<String> findByEmail(String email);
18    Mono<Void> insert(String email);
19}

This reads naturally once you think in Reactor signals rather than if statements.

An Alternative Using hasElement

Sometimes you only care whether the Mono is empty, not what value it contains. In that case, hasElement() can make the intent explicit.

java
1Mono<String> runIfEmpty(Mono<String> source) {
2    return source.hasElement()
3        .flatMap(hasValue -> {
4            if (hasValue) {
5                return Mono.error(new IllegalStateException("Mono was not empty"));
6            }
7            return performAction();
8        });
9}

This style is readable, but it introduces an extra boolean stage. When you already have the original value and want to fail immediately if it exists, the flatMap plus switchIfEmpty version is usually more idiomatic.

Think in Terms of Intent

A useful way to phrase the requirement is this:

  • "presence is an error"
  • "absence triggers work"

Once you say it that way, the operator choice becomes much easier. Avoid trying to force imperative if else thinking into the middle of a reactive chain. Reactor becomes clearer when each branch corresponds to a signal path.

Common Pitfalls

The most common mistake is writing the fallback action eagerly instead of lazily. Use Mono.defer when the action has side effects or should only be constructed when the source is actually empty.

Another mistake is using defaultIfEmpty for this problem. defaultIfEmpty injects a value, but it does not model "do work only when empty" as clearly as switchIfEmpty.

A third pitfall is forgetting that map cannot return a Mono. If your error or fallback branch is itself reactive, use flatMap.

Summary

  • Convert the non-empty case into Mono.error(...) with flatMap.
  • Use switchIfEmpty(...) for the action that should run only when the source is empty.
  • Wrap side-effecting fallback logic in Mono.defer(...) so it stays lazy.
  • 'hasElement() can work too, but flatMap plus switchIfEmpty is usually more idiomatic.'
  • Think in Reactor signals, not imperative branching.

Course
Intermediate
27 lessons
14 hours
OOD Fundamentals

Master object-oriented design from first principles, SOLID, design patterns, and classic interview problems with hands-on coding.

View the course
Data Structures & Algorithms practice on Codemia

Step through 300+ coding interview problems with animated visualisers that show the data structure changing as the code runs. The Blind 75 are free.

Practice algorithms