From e32e3359c2a35b7da4d812bd8de2eafa560a7672 Mon Sep 17 00:00:00 2001 From: Sangmin Park Date: Fri, 11 Jul 2025 14:14:36 +0900 Subject: [PATCH] Support virtual threading with JDK HTTP clients Update JDK HTTP Clients so that the use virtual threads when `Threading.VIRTUAL` is active. See gh-46404 Signed-off-by: Sangmin Park --- .../JdkClientHttpRequestFactoryBuilder.java | 7 +++++++ .../HttpClientAutoConfiguration.java | 20 +++++++++++++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/module/spring-boot-http-client/src/main/java/org/springframework/boot/http/client/JdkClientHttpRequestFactoryBuilder.java b/module/spring-boot-http-client/src/main/java/org/springframework/boot/http/client/JdkClientHttpRequestFactoryBuilder.java index 02229c39746..d971d47a334 100644 --- a/module/spring-boot-http-client/src/main/java/org/springframework/boot/http/client/JdkClientHttpRequestFactoryBuilder.java +++ b/module/spring-boot-http-client/src/main/java/org/springframework/boot/http/client/JdkClientHttpRequestFactoryBuilder.java @@ -19,6 +19,7 @@ package org.springframework.boot.http.client; import java.net.http.HttpClient; import java.util.Collection; import java.util.List; +import java.util.concurrent.Executors; import java.util.function.Consumer; import org.jspecify.annotations.Nullable; @@ -75,6 +76,12 @@ public final class JdkClientHttpRequestFactoryBuilder this.httpClientBuilder.withCustomizer(httpClientCustomizer)); } + public JdkClientHttpRequestFactoryBuilder enableVirtualThreadExecutor() { + return this.withHttpClientCustomizer(builder -> + builder.executor(Executors.newVirtualThreadPerTaskExecutor()) + ); + } + @Override protected JdkClientHttpRequestFactory createClientHttpRequestFactory(ClientHttpRequestFactorySettings settings) { HttpClient httpClient = this.httpClientBuilder.build(asHttpClientSettings(settings.withReadTimeout(null))); diff --git a/module/spring-boot-http-client/src/main/java/org/springframework/boot/http/client/autoconfigure/HttpClientAutoConfiguration.java b/module/spring-boot-http-client/src/main/java/org/springframework/boot/http/client/autoconfigure/HttpClientAutoConfiguration.java index 68104da96df..b25d4f827d7 100644 --- a/module/spring-boot-http-client/src/main/java/org/springframework/boot/http/client/autoconfigure/HttpClientAutoConfiguration.java +++ b/module/spring-boot-http-client/src/main/java/org/springframework/boot/http/client/autoconfigure/HttpClientAutoConfiguration.java @@ -24,10 +24,13 @@ import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnThreading; import org.springframework.boot.autoconfigure.ssl.SslAutoConfiguration; +import org.springframework.boot.autoconfigure.thread.Threading; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.http.client.ClientHttpRequestFactoryBuilder; import org.springframework.boot.http.client.ClientHttpRequestFactorySettings; +import org.springframework.boot.http.client.JdkClientHttpRequestFactoryBuilder; import org.springframework.boot.ssl.SslBundles; import org.springframework.boot.util.LambdaSafe; import org.springframework.context.annotation.Bean; @@ -64,18 +67,31 @@ public final class HttpClientAutoConfiguration implements BeanClassLoaderAware { @Bean @ConditionalOnMissingBean - ClientHttpRequestFactoryBuilder clientHttpRequestFactoryBuilder( + @ConditionalOnThreading(Threading.PLATFORM) + ClientHttpRequestFactoryBuilder clientHttpRequestFactoryBuilderOnPlatform( ObjectProvider> clientHttpRequestFactoryBuilderCustomizers) { ClientHttpRequestFactoryBuilder builder = this.factories.builder(this.beanClassLoader); return customize(builder, clientHttpRequestFactoryBuilderCustomizers.orderedStream().toList()); } + @Bean + @ConditionalOnMissingBean + @ConditionalOnThreading(Threading.VIRTUAL) + ClientHttpRequestFactoryBuilder clientHttpRequestFactoryBuilderOnVirtual( + ObjectProvider> clientHttpRequestFactoryBuilderCustomizers) { + ClientHttpRequestFactoryBuilder builder = this.factories.builder(this.beanClassLoader); + if (builder instanceof JdkClientHttpRequestFactoryBuilder jdk) { + return customize(jdk.enableVirtualThreadExecutor(), clientHttpRequestFactoryBuilderCustomizers.orderedStream().toList()); + } + return customize(builder, clientHttpRequestFactoryBuilderCustomizers.orderedStream().toList()); + } + @SuppressWarnings("unchecked") private ClientHttpRequestFactoryBuilder customize(ClientHttpRequestFactoryBuilder builder, List> customizers) { ClientHttpRequestFactoryBuilder[] builderReference = { builder }; LambdaSafe.callbacks(ClientHttpRequestFactoryBuilderCustomizer.class, customizers, builderReference[0]) - .invoke((customizer) -> builderReference[0] = customizer.customize(builderReference[0])); + .invoke((customizer) -> builderReference[0] = customizer.customize(builderReference[0])); return builderReference[0]; }