According to a [request of the OP](https://stackoverflow.com/questions/78554636/why-java-virtual-threads-use-forkjoinpool#comment138531825_78554636) to configure `VirtualThread` with a custom thread pool, the following solution is discussed below.
As of JDK 21 there is no way to set up a custom `Executor`/`scheduler` for `VirtualThread` class: this class along with `ThreadBuilders.VirtualThreadBuilder` and `VirtualThreadFactory` are `java.lang` package-private and the only way to do so is to use Reflection.
public static Thread.Builder customVirtualThreadPool(Executor executor) throws ReflectiveOperationException {
var ctor = Class.forName("java.lang.ThreadBuilders$VirtualThreadBuilder")
.getDeclaredConstructor(Executor.class);
ctor.setAccessible(true);
return (Thread.Builder) ctor.newInstance(executor);
}
As access to `java.lang` classes is attempted it might be necessary to explicitly allow it in application parameters
--add-opens=java.base/java.lang=ALL-UNNAMED
as [Module java.base does not "opens java.lang"](https://stackoverflow.com/questions/74006627/module-java-base-does-not-opens-java-lang-java-17-0-4-1) thread suggests.
Note that this can be used to set up custom `ForkJoinPool` instance as an actual pool in order to get an access to work-stealing count.
For Spring Boot/Tomcat application further configuration does not differ from configuration of any other such `Executor`.
As usual, to run Tomcat worker threads on this `Excecutor`, the ones that executes `@Controller`s methods, annotated with `@RequestMapping` and analogous, customize Tomcat Protocol Handler:
@Bean
public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() {
return protocolHandler -> {
try {
protocolHandler.setExecutor(
customVirtualThreadPool(Executors.newCachedThreadPool())::start);
} catch (ReflectiveOperationException e) {
throw new BeanInitializationException("Cannot create custom Virtual Thread Executor", e);
}
};
}
Similarly, to run Spring-controlled asynchronous methods on this `Executor`, configuration will look like this:
@Configuration
@EnableAsync
public class AppConfig {
@Bean
public TaskExecutor taskExecutor() {
try {
return new TaskExecutorAdapter(customVirtualThreadPool(Executors.newCachedThreadPool())::start);
} catch (ReflectiveOperationException e) {
throw new BeanInitializationException("Cannot create custom Virtual Thread Executor", e);
}
}
}
and the asynchronous method like this:
@Service
@Async // will use `taskExecutor` by default, if there are more than one in the app, then Bean name can be specified
public class AsyncService {
public void doSomething() {
...
}
}
Note that presence of `spring.threads.virtual.enabled = true` in application property file is not required in both cases.
The above is applicable to Spring Boot 3.2.0; be cautious with older Tomcats, they may or may not support the configuration above.