Log inside Mono.error gets outputted twice

Viewed 778

For some strange reason, the "User not found" log gets outputted twice in the logs of my application, despite calling findUserById only once. I am not sure what causes this problem.

Is there a better way to approach this (logging and throwing an exception)?

Please note that the findById call is of an API.

Edit: It appears that the exception is thrown only once. Also, if I replace Mono.error with Mono.defer, the log is printed twice as well.

public Mono<User> getUser(String id) {
    
    Mono<User> thisIsEmpty = getNoUser(); // Assume that this is empty

    return Mono.defer(() -> thisIsEmpty.switchIfEmpty(Mono.defer(() -> findUserById(id))));
}


public Mono<User> findUserById(String id) {
        log.info("This is printed once.");
        Mono<User> user = repository.findById(id).switchIfEmpty(Mono.error(() -> { // findById is an API call of a library I use
            log.error("User not found (this is printed twice)"); // Gets printed twice
            throw new UserException(MY_ERROR_CODE, 401);
        }));

        user.subscribe(User -> ... // Do something if it is not empty

        return user;
}
2 Answers

Well, from Mono#error docs:

Create a Mono that terminates with an error immediately after being subscribed to. The Throwable is generated by a Supplier, invoked each time there is a subscription and allowing for lazy instantiation.

First your subscription is here:

user.subscribe(User -> ...

And I assume you have another subscription in code, that uses getUser.

That's why it's printed twice.

As @Eugene mention your problem is that you probably subscribe twice.

If you want to call it once then your method should be rewriten

public Mono<User> findUserById(String id) {
    log.info("This is printed once.");
    return repository.findById(id)
            .switchIfEmpty(Mono.error(() -> { // findById is an API call of a library I use
                log.error("User not found (this is printed twice)"); // Gets printed twice
                throw new UserException(MY_ERROR_CODE, 401);
            }))
            .flatMap(user-> {
                // do smth here 
                return Mono.just(user);
            });


    return user;
}

Reactive is a bit different so when you return mono it wont be executed until you subscribe to it (or block which you should not do until you must).

So basically what is happening in your case first time you "execute" when you do

user.subscribe(User -> ... // Do something if it is not empty

Second time is probably you have controller which is returning mono and spring boot is subscribing

Also not anything you do inside of you service subscribe wont be sent outside of it. You should use flatMap or any other Reactive operators for that.

Related