Files
tech-log-backend/docs/httpclient/support-matrix.md
T

6.0 KiB

HTTP Client Platform — Support Matrix

Grades follow design §6 and §29. A row is Stable only when the cross-transport contract suite proves it; anything the suite cannot prove is Experimental and says so.

Spring API

API Grade Role Constraint
RestClient Stable Blocking execution Bounded concurrency and an effective deadline are mandatory
WebClient Stable Reactive, streaming, SSE No blocking work on the event loop
HTTP Service Client (@HttpExchange) Default Declarative typed client Operation metadata is mandatory
RestTemplate Migration only Moving existing calls No new profile or feature
Generic Exchange (H2) Restricted Dynamic method, path, body Base URL and policy are immutable
Dynamic Target (H3) Restricted User-supplied URL Separate SSRF policy; inherits no credential
Native engine Internal Engine-specific configuration Never an application-facing API

Transports

Transport Blocking Reactive HTTP/1.1 HTTP/2 HTTP/3 Grade Verified by
Apache HttpClient 5 (classic) yes no yes no no Stable (blocking default) httpClientStableContractTest, NegotiatedProtocolContractTest
JDK HttpClient yes sendAsync yes yes (TLS/ALPN) no Stable (lightweight, blocking HTTP/2) NegotiatedProtocolContractTest
Reactor Netty limited yes yes yes experimental Stable (reactive default) NegotiatedProtocolContractTest
Jetty facade yes yes yes yes Experimental Http3OptInTest only
Simple request factory yes no limited no no Local test only rejected in production by ClientProfileValidator

Apache is HTTP/1.1 here, and why

Design §6.2 grades Apache HttpClient 5 as HTTP/2-capable, and the library is — in its async client. Spring's HttpComponentsClientHttpRequestFactory drives the classic client, which speaks HTTP/1.1 only. NegotiatedProtocolContractTest measures this rather than assuming it: the classic client fails outright against a prior-knowledge h2c server.

So ApacheBlockingTransportProvider.capabilities() declares HTTP/1.1, and a profile that pairs Apache with HTTP_2 is rejected at startup instead of quietly running HTTP/1.1 while this table claims otherwise. Blocking HTTP/2 is served by the JDK transport; reactive HTTP/2 by Reactor Netty. Both are measured from the client after a real TLS handshake, not read from configuration.

The JDK transport declares routeScopedPool=false, boundedPendingAcquireQueue=false, and dynamicTargetStable=false. A profile that needs any of those is rejected at startup rather than served with weaker guarantees. Choosing between Apache and JDK is therefore a real trade: Apache gives route-scoped pooling and Dynamic Target pinning, JDK gives HTTP/2.

Capability gates

Capability Gate
Dynamic Target (H3) Apache and Reactor Netty only; JDK and Jetty are rejected
HTTP/3 experimentalAcknowledgement must equal I_ACCEPT_HTTP3_EXPERIMENTAL_SEMANTICS
Cross-origin redirect opt-in per profile; credentials are stripped on the hop
Retry evidence-based; never enabled by HTTP method alone

CI matrix

Profile Frequency Release gate Task
Spring Framework 7.0 (repository baseline) every PR required spring70CompatibilityTest
Spring Framework 6.2 API surface every PR required spring62CompatibilityTest
Apache HC5 + RestClient every PR required httpClientStableContractTest -Phttpclient.contract.transports=apache
JDK HttpClient + RestClient every PR required httpClientStableContractTest -Phttpclient.contract.transports=jdk
Reactor Netty + WebClient every PR required httpClientStableContractTest -Phttpclient.contract.transports=reactor
SSRF / cardinality suite every PR required httpClientSecurityTest
Toxiproxy fault suite nightly, release required httpClientFailureInjectionTest
Event-loop blocking (BlockHound) every PR required httpClientBlockHoundTest
Performance certification nightly, release required httpClientPerformanceTest -Pperformance.assertions.enabled=true
Jetty HTTP/3 nightly Experimental, non-blocking test -Phttp3.tests.enabled=true

Known limitation of the Spring 6.2 lane

This repository's Spring Boot 4.0 baseline pins Spring Framework 7, so a real 6.2 runtime cannot be resolved here. spring62CompatibilityTest therefore verifies the API surface: the common packages must not reference any Spring 7-only type, and org.springframework.web.service.registry is confined to …httpclient.spring7. Executing the suite against an actual 6.2 distribution requires a host project on that line. This limitation is stated rather than hidden behind a passing check.

What the suites do not prove

Stated so the matrix is read as a measurement rather than an aspiration.

Gap Why What is proven instead
HTTP/2 frame injection (REFUSED_STREAM, arbitrary GOAWAY) The fixture server exposes no frame-level control, and a purpose-built h2 server is a larger dependency than the guarantee is worth here Http2EvidenceMapperTest proves the frame → evidence mapping, and NegotiatedProtocolContractTest proves h2 is really negotiated
Netty buffer-leak detection Netty reports a leak when an unreferenced buffer is collected, which the suite does not force NettyLeakDetectionExtension asserts the PARANOID detector is live and reports nothing; explicit release assertions in the streaming suites are the primary guarantee
Spring 6.2 runtime This repository's Boot 4.0 baseline pins Spring 7 spring62CompatibilityTest confines the common packages to the 6.2 API surface
Performance latency baseline Numbers measured on a build agent are not a certification httpClientPerformanceTest asserts structural bounds unconditionally; latency and heap bounds run under -Pperformance.assertions.enabled=true