Snapshot of the in-flight state that already existed, identically, in both this worktree and the main checkout before this session began: the initial HTTP Client platform implementation (previously untracked), the redis-lab removal, and the JPA / object-storage / notification integration work. Kept separate from this session's HTTP Client review response, which lands in the following commit, so the two bodies of work stay reviewable apart. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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 |