The problem is due to how a completed exceptionally CompletableFuture handles the exception in subsequent stages.

As stated in the CompletableFuture javadoc

[..] if a stage's computation terminates abruptly with an (unchecked) exception or error, then all dependent stages requiring its completion complete exceptionally as well, with a CompletionException holding the exception as its cause. [..]

In my case, the thenApply method creates a new instance of CompletionStage that wraps with a CompletionException the original UserNotFoundException :(

Sadly, the controller advice does not perform any unwrapping operation. Zalando developers also found this problem: Async CompletableFuture append errors

So, it seems to be not a good idea to use CompletableFuture and controller advice to implement asynchronous controllers in Spring.

A partial solution is to remap a CompletableFuture<T> to a DeferredResult<T> . In this blog, an implementation of a possible Adapter was given.

public class DeferredResults { private DeferredResults() {} public static <T> DeferredResult<T> from(final CompletableFuture<T> future) { final DeferredResult<T> deferred = new DeferredResult<>(); future.thenAccept(deferred::setResult); future.exceptionally(ex -> { if (ex instanceof CompletionException) { deferred.setErrorResult(ex.getCause()); } else { deferred.setErrorResult(ex); } return null; }); return deferred; } }

So, my original controller would change to the following.

@GetMapping("/{id}/address") public DeferredResult<Address> getAddress(@PathVariable String id) { return DeferredResults.from(service.findById(id).thenApply(User::getAddress)); }

I cannot understand why Spring natively supports CompletableFuture as return values of a controller, but it does not handle correctly in controller advice classes.

Hope it helps.