there are tons of variations, depending on your logic and API. The closest to virtual threads is ForkJoinPool and RecursiveTask, where you can have code like regular blocking code:
var f = async_api_returns_future();
...
var res = f.join();
but join() won't block OS/JVM thread, but make it to perform other tasks in the queue.
Or you can design API which will receive executorService as params, and run callback there, e.g.:
async_call(Callable callback, ExecutorService threadPool);