HTTP/3 (QUIC) Support
MockServer can optionally accept HTTP/3 requests over QUIC (UDP). HTTP/3 is the third major version of the HTTP protocol, using QUIC as the transport instead of TCP. This is useful when you want to test clients that connect using HTTP/3.
HTTP/3 requests are routed through the same expectation matching pipeline as HTTP/1.1 and HTTP/2 requests. This means all features work the same way: expectation matching, response actions, template actions, recording for verification, and proxy forwarding.
Enabling HTTP/3
HTTP/3 is disabled by default (http3Port defaults to 0). Set it to a non-zero UDP port number to start the listener:
| Method | Example |
|---|---|
| Java system property | -Dmockserver.http3Port=8443 |
| Environment variable | MOCKSERVER_HTTP3_PORT=8443 |
| Configuration API | Configuration.configuration().http3Port(8443) |
When http3Port is 0 (the default), the HTTP/3 listener is not started and has no impact on the existing TCP/HTTP server.
HTTP/3 also needs a native library that is not in the default artifacts (see native platform requirement). In Docker, use the -http3 image and publish the port as UDP:
docker run -d --rm -p 1080:1080 -p 8443:8443/udp -e MOCKSERVER_HTTP3_PORT=8443 mockserver/mockserver:9.0.0-http3
Lifecycle
When http3Port is configured, the HTTP/3 server starts automatically with MockServer and stops when MockServer shuts down. No separate lifecycle management is needed.
If the native QUIC library is not available (see native platform requirement), MockServer fails to start and prints the fixes for your version and platform, for example:
HTTP/3 is enabled (http3Port=8443) but the QUIC native library for linux-x86_64 is not available, so MockServer cannot start.
HTTP/1.1 and HTTP/2 need nothing extra; HTTP/3 is experimental and its native library ships separately.
Fix it with one of:
- Docker, Docker Compose or Kubernetes: use the image mockserver/mockserver:<version>-http3 (Helm: --set image.variant=http3)
- standalone jar: run mockserver-netty-<version>-jar-with-dependencies-http3.jar instead of the default jar
- Maven or Gradle: add the runtime dependency io.netty:netty-codec-native-quic:<netty version>:linux-x86_64
- or remove http3Port to run without HTTP/3
Earlier versions logged a warning and started anyway without HTTP/3, which meant the UDP port you asked for was silently never listening — this is what every earlier Docker image did. Refusing to start and telling you is the safer behaviour. To run without HTTP/3, remove http3Port.
MockServer also fails to start if the HTTP/3 server cannot be started on http3Port for any other reason, most often because that UDP port is already in use. It stops the HTTP and HTTPS listeners it had already opened and reports one line naming the port and the cause, for example:
HTTP/3 is enabled (http3Port=8443) but UDP port 8443 could not be bound, so MockServer cannot start: free the port if another application holds it, choose a different http3Port, or remove http3Port to run without HTTP/3 (underlying error: BindException: Address already in use)
The most likely holder of the port is another MockServer started with the same http3Port: two embedded servers in one JVM, test forks running in parallel that share one mockserver.http3Port system property or properties file, or several instances on one host started from the same configuration. Earlier versions let the first one serve HTTP/3 while the others quietly served only HTTP/1.1 and HTTP/2; now every one but the first fails to start. Give each instance its own http3Port, or set it only on the instance that should serve HTTP/3. The same line with BindException: Permission denied means the process is not allowed to bind that port (on Linux, a port below 1024 without the privilege to bind it): choose a port of 1024 or above.
What you see depends on how you run MockServer:
- Command line, Docker and Helm — the process exits with a non-zero status, so a container stops (and under Kubernetes restarts and reports CrashLoopBackOff) instead of running without HTTP/3. When the port could not be bound it prints that line alone; for any other failure to start HTTP/3 (for example TLS settings from which no certificate chain can be built) it prints the line, which then reads its server could not start on UDP port ..., followed by a stack trace. Read the container log for it.
- Embedded in a JVM (ClientAndServer.startClientAndServer(...), new MockServer(...), the JUnit 4 rule, the JUnit 5 extension and the Spring test listener) — the start throws a RuntimeException (org.mockserver.netty.http3.Http3StartupException) carrying that message and the original error as its cause, as a TCP port that is already in use does.
- Maven plugin and Spring Boot starter — the plugin's start, run and runForked goals fail the build with that message, and the mockserver-spring-boot-starter fails the application context's start-up (a BeanCreationException whose cause is the exception above).
Earlier versions logged a warning (exception starting HTTP/3 server on port ... - HTTP/3 disabled) and started without HTTP/3, so a deployment whose HTTP/3 port was unavailable kept running and served only HTTP/1.1 and HTTP/2. Such a deployment now does not start until the port is free, http3Port is changed, or http3Port is removed. http3Port cannot ask for a free port to be chosen: 0 means HTTP/3 is off.
The bound HTTP/3 port can be retrieved programmatically via MockServer.getHttp3Port().
Checking status at runtime
GET /mockserver/http3status reports whether the HTTP/3 listener is active, which UDP port it is bound to, and how many QUIC connections are currently open — useful to confirm HTTP/3 actually started (rather than silently serving only HTTP/1.1 and HTTP/2) without parsing the startup log:
curl -sf http://mockserver:1080/mockserver/http3status
# {"enabled":true,"port":8443,"activeConnections":2}
enabled is true only when http3Port is configured and bound (it is false, with port -1 and activeConnections 0, when HTTP/3 is off). This is a control-plane read, so it is subject to control plane authorisation when it is enabled, the same as /mockserver/dashboard and /mockserver/openapi.yaml.
Native platform requirement
HTTP/3 uses the mainline io.netty:netty-codec-http3 library (Netty 4.2), which requires a native BoringSSL library for the QUIC transport.
These native binaries are not in the default artifacts. They add about 11 MB across the five supported platforms — the largest single item in the standalone jar — for a feature that is experimental and does nothing unless http3Port is set, so they are packaged separately and everyone else stops paying to download them. Pick whichever row matches how you run MockServer:
| How you run MockServer | How to get HTTP/3 |
|---|---|
| Docker, Docker Compose or Kubernetes | use the mockserver/mockserver:<version>-http3 image (snapshot-http3 for the latest snapshot) — the standard image plus the native library for its architecture (linux/amd64 or linux/arm64), otherwise identical. With the Helm chart, set image.variant=http3. The other images cannot load the native library, even if you add it to /libs |
| Standalone jar | download the jar-with-dependencies-http3 classifier instead of the default jar — it is otherwise identical |
| Maven or Gradle | depend on org.mock-server:mockserver-netty, which already brings io.netty:netty-codec-native-quic for your platform; if you exclude it or manage Netty yourself, add it with your platform's classifier at the same Netty version as the rest of the server. mockserver-netty-no-dependencies cannot load it |
Native binaries exist for these platforms:
- linux-x86_64
- linux-aarch_64
- osx-x86_64
- osx-aarch_64
- windows-x86_64
On any other platform there is no native library to add, and HTTP/3 cannot be used. Leave http3Port unset; HTTP/1.1 and HTTP/2 are unaffected and need nothing extra.
TLS
The HTTP/3 server uses MockServer's configured TLS certificate material — the same private key and certificate chain used by the HTTPS server. This means the TLS configuration properties (privateKeyPath, x509CertificatePath, etc.) apply to HTTP/3 as well.
If no custom certificate is configured, MockServer's auto-generated certificate (created via BouncyCastle) is used, which is suitable for testing.
Tuning / configuration
The QUIC transport parameters can be tuned for high-throughput or long-lived connection tests. Each has a sensible default and rarely needs changing:
- http3MaxIdleTimeout — idle timeout in milliseconds before a QUIC connection is closed
- http3InitialMaxData — connection-level flow-control limit (bytes)
- http3InitialMaxStreamDataBidirectional — per-stream flow-control limit (bytes)
- http3InitialMaxStreamsBidirectional — maximum number of concurrent bidirectional streams
- http3QpackMaxTableCapacity — QPACK dynamic header-table capacity (bytes)
See the HTTP/3 configuration properties on the configuration page for defaults, value ranges, and the full four-form reference (Java code, system property, environment variable, property file).
What works
- Expectation matching — HTTP/3 requests are matched against expectations identically to HTTP/1.1 and HTTP/2
- Response actions — all response action types (static, template, callback, etc.)
- Streaming / SSE responses — Server-Sent Events, chunked proxy forwarding, and LLM streaming responses are fully supported. Each body chunk is delivered as an HTTP/3 DATA frame with backpressure; the QUIC stream is cleanly shut down on completion or error
- Request recording — HTTP/3 requests are recorded for verification
- Proxy forwarding — unmatched requests can be forwarded when proxy mode is configured
- Request body — text and binary request bodies are read and matched correctly
- Configurable TLS — uses the same TLS certificate as the HTTPS server
- Lifecycle integration — starts and stops automatically with MockServer
- Actionable startup failure — if http3Port is set without the native QUIC library, start-up fails with a message naming the artifact to use, and if the HTTP/3 server cannot start on that port (for example the UDP port is already in use) start-up fails with a message naming the port and the cause, rather than silently leaving the port unserved
- Alt-Svc auto-discovery — when http3Port is set, MockServer automatically adds an Alt-Svc header to all TCP (HTTP/1.1 and HTTP/2) responses advertising the HTTP/3 endpoint. HTTP/3-capable clients will automatically upgrade to QUIC on subsequent requests, with transparent fallback to HTTP/2 or HTTP/1.1 if QUIC is unavailable. The advertisement max-age is configurable via http3AltSvcMaxAge (default 86400 seconds). Set http3AdvertiseAltSvc=false to disable advertising while keeping HTTP/3 enabled
- W3C trace-context propagation — traceparent and tracestate headers on inbound HTTP/3 requests are parsed and stored for OpenTelemetry span parenting, matching the TCP path. When otelPropagateTraceContext is enabled, the headers are copied to outbound HTTP/3 responses. When otelGenerateTraceId is enabled, a trace context is generated for requests without a traceparent header. Both features are default-off (same config as TCP)
- mTLS client-certificate capture — when mTLS is configured (tlsMutualAuthenticationRequired or tlsMutualAuthenticationCertificateChain), client certificates are captured from the QUIC TLS handshake and attached to the request, enabling cert-based expectation matching and verification over HTTP/3
- MCP over HTTP/3 — the Model Context Protocol Streamable HTTP transport works over HTTP/3 with the same control-plane authentication (JWT / mTLS) and CORS behaviour as the TCP path; the request processing is shared between the two transports
- gRPC over HTTP/3 — unary, server-streaming, and bidirectional-streaming gRPC calls work over HTTP/3 with correct trailing-HEADERS grpc-status framing. Bidirectional streaming requires grpcBidiStreamingEnabled (the same flag as the TCP path)
- CONNECT-UDP (MASQUE) relay — a working forward-proxy relay for tunnelling UDP through MockServer (RFC 9298). When enabled via http3ConnectUdpEnabled (default false), the server advertises SETTINGS_ENABLE_CONNECT_PROTOCOL (RFC 9220) and handles extended-CONNECT requests with :protocol=connect-udp: it opens a UDP socket to the target authority and relays datagrams bidirectionally (one HTTP/3 DATA frame per UDP datagram). Normal (non-CONNECT) HTTP/3 requests are unaffected. Each tunnel established (INFO), each refused request or target and each relay failure (WARN or ERROR) appears in MockServer's log and the dashboard's log, filtered by logLevel; a client ending its tunnel is logged only at DEBUG. Security — this is an open UDP relay. When enabled there is no restriction on the relay target, so a client can reach any UDP host:port reachable from MockServer (including private networks, loopback, and cloud metadata endpoints such as 169.254.169.254). It is intended for controlled test environments only — leave it disabled (the default) and never expose a CONNECT-UDP–enabled HTTP/3 port to untrusted clients. See HTTP/3 CONNECT-UDP (MASQUE) Enabled on the configuration page
Limitations
- Codec API maturity — the HTTP/3 codec has graduated from the Netty incubator into mainline Netty 4.2, but the API may still evolve in future releases
- Large responses on Linux — MockServer asks for a 4 MiB UDP receive buffer for its HTTP/3 port, but Linux limits it to net.core.rmem_max (often only 208 KiB). With a small buffer, packets the client sends back while a large response is downloading can be dropped; most clients recover, but some (for example curl --http3 on Debian or Ubuntu, which uses OpenSSL's QUIC support) can stall on a response of several megabytes. If you serve large HTTP/3 responses from a Linux host, raise the limit on the host with sysctl -w net.core.rmem_max=4194304 (a Docker container cannot change it)
HTTP/3 parity and known differences
All expectation matching, actions, recording, verification, HTTP chaos profiles, forward-proxy, trace-context propagation, mTLS, MCP (Model Context Protocol Streamable HTTP, including control-plane authentication), and gRPC (unary, server-streaming, and bidirectional streaming) work over HTTP/3, matching the TCP (HTTP/1.1 and HTTP/2) path.
Inherently not applicable over HTTP/3
- TCP chaos (TcpChaosHandler) — QUIC has no TCP RST/FIN/slow_close semantics. HTTP-level chaos profiles (latency, error responses, degradation ramp) DO work over HTTP/3
- WebSocket callbacks + dashboard WebSocket — RFC 6455 HTTP/1.1 Upgrade has no HTTP/3 equivalent. The analog is WebTransport (not yet implemented)
- HTTP/1.1 framing fixups — handlers like PreserveHeadersNettyRemoves and EarlyMatchingHandler are HTTP/1.1-specific and have no meaning in HTTP/3
Not yet supported (deferred)
- Per-connection dashboard detail — the dashboard shows the HTTP/3 port and an aggregate active-connection count; per-connection detail (remote address, stream count, duration) is deferred
Previously deferred, now supported: MCP-over-HTTP/3 (shared Streamable-HTTP processor with full control-plane authentication) and gRPC-over-HTTP/3 (unary, server-streaming, and bidirectional streaming) both work over HTTP/3.