I found a solution after lots of debugging to understand the internals:
VertxContext.getOrCreateDuplicatedContext(_vertx).executeBlocking(
() -> {
MDC.put(xxx);
operation.accept(tenantId);
logger.info(xxx);
return null;
});
VertxMDC creates a duplicate context from the current context unless the current context is already a duplicated context.
Without getOrCreateDuplicatedContext(), the currentContext inside the operation is the EventLoop context (in my case), so every MDC call (put, get) creates its own duplicated context and stores/retrieves data in a storage that no one else will ever use.