Skip to content

Wire JDK HttpClient with virtual-thread executor when spring.threads.virtual.enabled=true#4155

Closed
venkatapgummadi wants to merge 1 commit intospring-cloud:mainfrom
venkatapgummadi:fix/4150-jdk-httpclient-virtual-threads
Closed

Wire JDK HttpClient with virtual-thread executor when spring.threads.virtual.enabled=true#4155
venkatapgummadi wants to merge 1 commit intospring-cloud:mainfrom
venkatapgummadi:fix/4150-jdk-httpclient-virtual-threads

Conversation

@venkatapgummadi
Copy link
Copy Markdown

Fixes #4150.

Problem

When spring.threads.virtual.enabled=true is set, Spring Boot switches the inbound Tomcat handlers to virtual threads, but Spring Cloud Gateway MVC's outbound JDK HttpClient still falls back to Executors.newCachedThreadPool() — spawning ~145 platform threads named HttpClient-N-Worker-M and capping proxy throughput at roughly 25% of what the virtual-thread front end can sustain.

The reporter's benchmark on the issue:

Metric Before After
Throughput @ c=200 2.3K req/s 9.8K req/s
P99 latency 439 ms 152 ms
Platform threads 145 0

Fix

Registers a ClientHttpRequestFactory bean in GatewayServerMvcAutoConfiguration that builds a JdkClientHttpRequestFactory whose underlying java.net.http.HttpClient is wired with a VirtualThreadTaskExecutor, so outbound proxy calls run on virtual threads end-to-end.

The bean is conditional on:

  • spring.threads.virtual.enabled=true
  • java.net.http.HttpClient on the classpath
  • JDK being the chosen outbound factory — explicit via spring.http.clients.imperative.factory=jdk, or auto-detected when no Apache / Jetty / Reactor Netty is on the classpath
  • the user not having defined their own ClientHttpRequestFactory (@ConditionalOnMissingBean)

Tests

Four new unit tests in GatewayServerMvcAutoConfigurationTests covering each conditional path:

  • bean is not registered by default
  • bean is registered and wired with a VirtualThreadTaskExecutor when virtual threads are enabled
  • bean is not registered when spring.http.clients.imperative.factory=simple
  • bean backs off when the user provides their own ClientHttpRequestFactory

The Java-21-specific tests are gated with @EnabledForJreRange(min = JRE.JAVA_21). All 9 tests pass locally on JDK 21.

…virtual.enabled=true

When spring.threads.virtual.enabled=true is set, Spring Boot switches the inbound Tomcat handlers to virtual threads, but Spring Cloud Gateway MVC's outbound JDK HttpClient still falls back to Executors.newCachedThreadPool(), spawning ~145 platform threads and capping proxy throughput at roughly 25% of what virtual threads can sustain (2.3K -> 9.8K req/s, P99 439ms -> 152ms in the reporter's benchmark).

This change registers a ClientHttpRequestFactory bean that explicitly wires the JDK HttpClient with a VirtualThreadTaskExecutor so outbound proxy calls run on virtual threads end-to-end.

Fixes spring-cloudgh-4150

Signed-off-by: Venkata Pavan Kumar Gummadi <venkata.p.gummadi@ieee.org>
@spencergibb
Copy link
Copy Markdown
Member

While I appreciate the enthusiasm, the open issue is still marked for triage. Conversation will happen on the issue before any time is taken here.

@venkatapgummadi
Copy link
Copy Markdown
Author

While I appreciate the enthusiasm, the open issue is still marked for triage. Conversation will happen on the issue before any time is taken here.

Apologies for jumping ahead of triage on this I'll move the discussion to #4150 with a short summary of the approach I prototyped, and happy to hold any further work until the issue has been triaged and an approach is agreed upon. Let me know if you'd prefer I close this PR in the meantime; happy to do so.

@spencergibb
Copy link
Copy Markdown
Member

Happy to keep it in waiting for triage. I commented on the other issue.

@spencergibb
Copy link
Copy Markdown
Member

spencergibb commented Apr 30, 2026

Based on Phil's comment, I'm going to close this for now #4150 (comment)

@venkatapgummadi venkatapgummadi deleted the fix/4150-jdk-httpclient-virtual-threads branch May 1, 2026 10:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Gateway MVC: outbound RestClient (JDK HttpClient) does not honor spring.threads.virtual.enabled=true, causing ~4x lower throughput

3 participants