Skip to content

chore(deps): update dependency org.mock-server:mockserver-netty to v9 - #315

Merged
dtoxvanilla1991 merged 1 commit into
mainfrom
renovate/org.mock-server-mockserver-netty-9.x
Oct 22, 2026
Merged

dtoxvanilla1991 merged 1 commit into
mainfrom
renovate/org.mock-server-mockserver-netty-9.x

Conversation

@renovate

@renovate renovate Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Note

This PR body was truncated due to platform limits.

This PR contains the following updates:

Package Change Age Confidence
org.mock-server:mockserver-netty (source) 7.6.0 → 9.1.0 age confidence

Release Notes

mock-server/mockserver-monorepo (org.mock-server:mockserver-netty)

v9.1.0

Security
  • Control-plane mTLS could be bypassed by sending the CA certificate after an untrusted client certificate. With controlPlaneTLSMutualAuthenticationRequired on, MockServer 5.13.0 to 9.0.0 accepted a control-plane request if any certificate the client sent could be validated against controlPlaneTLSMutualAuthenticationCAChain. A CA certificate is public, so anyone who could reach the control plane could send their own certificate followed by your CA certificate and create, read or delete expectations, change the configuration or reset the server, without the CA's private key. Also requiring data-plane mTLS (tlsMutualAuthenticationRequired) did not prevent this in the default set-up: the data-plane check also trusts MockServer's own CA, which by default is the CA bundled with MockServer, and its private key is published in the jar, so an attacker could sign their certificate with it. Data-plane mTLS prevented it only if MockServer had its own CA (certificateAuthorityCertificate and certificateAuthorityPrivateKey, or dynamicallyCreateCertificateAuthorityCertificate) and every CA in tlsMutualAuthenticationCertificateChain was private. MockServer now checks only the client's own certificate (the one whose private key it used for the connection): it must be signed by a CA in the chain, directly or through intermediate CA certificates the client sends after it, be in date, allow digital signatures if it lists key usages, allow client authentication if it lists extended key usages, and not be a CA certificate, a self-signed certificate or one of the certificates in the chain. A self-signed client certificate listed in controlPlaneTLSMutualAuthenticationCAChain is no longer accepted; sign client certificates with a CA instead. A controlPlaneTLSMutualAuthenticationCAChain that contains the bundled CA (or any certificate for its key) is now refused: MockServer does not start, PUT /mockserver/configuration answers 400, and any other change makes every control-plane request fail, where before only a warning was logged. If MockServerClient and MockServer share that property in one JVM (for example ClientAndServer or the JUnit rule) and you added the bundled CA so the client trusts MockServer's certificate, give MockServer its own CA with certificateAuthorityCertificate and certificateAuthorityPrivateKey and put that CA in the chain instead. The control-plane audit log now records the validated client certificate's subject as a verified principal (verified-mtls) unless OIDC supplied one; with mTLS and JWT both on, the audit principal is now the certificate subject instead of the unverified JWT sub. If a MockServer reachable by others had control-plane mTLS on, with or without data-plane mTLS, treat its expectations, recorded traffic and settings as possibly read or changed by someone else
  • mTLS (tlsMutualAuthenticationRequired) no longer accepts client certificates anyone can create (BREAKING for mTLS set-ups that use MockServer's default CA; see upgrading to 9). Up to 9.0.0, MockServer trusted client certificates signed by MockServer's own CA whenever mTLS was on, even with tlsMutualAuthenticationCertificateChain set. By default that CA is the one bundled with MockServer, and its private key is published in the MockServer jar, so anyone could create a client certificate, with any name, that MockServer accepted. That client also matched any client certificate condition in your expectations and, when control-plane authentication was off, could create, read or delete expectations. MockServer now never trusts the bundled CA for mTLS. If mTLS is on and nothing but the bundled CA would be trusted, or tlsMutualAuthenticationCertificateChain contains the bundled CA, MockServer does not start, PUT /mockserver/configuration answers 400, and the message names the fix: give MockServer its own CA with certificateAuthorityCertificate and certificateAuthorityPrivateKey, set dynamicallyCreateCertificateAuthorityCertificate=true, or set tlsMutualAuthenticationCertificateChain to the CA that signs your client certificates. With a chain set and the default CA, MockServer now trusts only the chain. For a throwaway local test, the new tlsMutualAuthenticationAllowBundledCa=true restores the old behaviour, and a warning is logged whenever the bundled CA becomes trusted (at startup, when a configuration change makes it trusted, and when the TLS context is rebuilt). This applies to HTTPS and HTTP/3. If a MockServer reachable by others relied on mTLS with the default CA, treat its expectations and recorded traffic as possibly read or changed by someone else
  • Dashboard: a crafted dashboard link can no longer make the dashboard reset, clear or stop the MockServer that serves it. The dashboard reads ?host=, ?port= and ?basePath= from its own address and used them unchecked to build every call it makes to MockServer, so a link such as …/mockserver/dashboard?basePath=/mockserver/reset%23 turned the dashboard's next routine call into /mockserver/reset as soon as someone opened it. These values now go through the same checks as a server typed into Set MockServer: a host must be a plain host name or IP address (no scheme, path, @ or #), a port a number from 1 to 65535, and a base path a plain path (no ?, #, %, \, // or ./.. segments). An invalid value is ignored, the dashboard uses the server that served it instead, and a warning at the top of the dashboard names what was ignored. A valid link that points the dashboard at a server on a different host now shows a warning naming that server, because what you create in the dashboard is sent there, with a button to switch back to the server that served the page; that server no longer decides whether dashboard analytics are on (the server that served the page still does). Valid links on the same host, such as ?port=1080 or ?basePath=/mock, work as before with no warning. A dashboard served behind a proxy under a path containing a percent-encoded character (for example a space) now falls back to the root path, with the same warning; pass ?basePath= to set it. The stricter base-path check also applies to Set MockServer, and a saved workspace target whose base path contains % or \ is dropped, so that workspace follows the dashboard's address again
  • Load scenarios: a STAIRS load shape with a huge steps value (for example 2147483647) could exhaust the server's memory when it was loaded with PUT /mockserver/loadScenario, before the stage limit was checked. A shape is now checked against the stage limit (loadGenerationMaxStages, 20 by default) before it is expanded, and rejected with 400 Bad Request if it is too big; no shape can expand to more than 10,000 stages. Stage durations so large that their total wrapped past the maximum run duration are now rejected too, and so is NaN or Infinity in any number in a load scenario (stage rates, shape parameters, step weights, the pacing value and thresholds); before, a NaN step weight made a weighted scenario fail once it ran. A VU shape value too big for a whole number of virtual users is now rejected by the virtual-user limit instead of silently wrapping to a small or negative count
  • Node, Python, Go, Rust and .NET clients: the callback and breakpoint WebSocket now carries the client's configured control-plane bearer token as Authorization: Bearer <token>, as control-plane HTTP calls already did, so callbacks and breakpoints keep working when MockServer requires JWT or OIDC control-plane authentication on that WebSocket. A token supplier is asked for a fresh token on each connection, including the Node and Python clients' reconnects. With no token configured nothing is sent, and a MockServer that does not require authentication ignores the header. In a browser the Node client cannot send it, because browsers do not let a page set headers on a WebSocket and MockServer accepts the token only in that header; use mutual TLS for browser callbacks and breakpoints when control-plane authentication is on. The Ruby client has no control-plane token option and the PHP client has no callback WebSocket, so neither changes
  • Log lines can no longer be forged through request data. The forward circuit breaker's log messages (which name the upstream, which can come from a request's Host header or HTTP/2 :authority) and the LLM conversation matcher's debug messages (which quote the request body) now write carriage returns, line feeds and other control characters as escapes such as \r and \n, so a crafted request cannot start a new log line
  • By default, web pages on other sites can no longer use MockServer's API or WebSockets from your browser (BREAKING; see upgrading to 9). Since 7.0.0, MockServer's own API (every /mockserver/... path, including MCP and metrics), the dashboard's live log and the callback WebSocket answered any web page, so a site you visited while MockServer was running could read recorded traffic, add or change expectations, and reset or stop the server. A new property, controlPlaneCorsAllowOrigin, now decides which browser origins may use them. Left blank (the default), it allows pages on localhost, 127.0.0.1 or [::1] (http or https, any port) and pages MockServer serves itself, such as the dashboard, when MockServer is reached by a name DNS rebinding cannot take over (see the next entry); set it to a comma-separated list of origins (or *) to allow others. It can only be set at startup: PUT /mockserver/configuration ignores it, with a warning, so a request to the control plane cannot widen it. Any request the control plane serves (any /mockserver/... path including MCP and metrics, a legacy path such as /reset or /expectation with a method the control plane serves there, the liveness path, and the /_mockserver_ui_websocket and /_mockserver_callback_websocket upgrades) that carries an Origin header from any other page, including Origin: null (a sandboxed iframe or a file:// page), is refused with 403 before it is handled and before any expectation can answer it, whatever method it uses and whether or not the browser sent a preflight first. Preflights for those requests are answered by MockServer without Access-Control-Allow-* headers. corsAllowOrigin no longer affects MockServer's own API: it, and enableCORSForAllResponses, still apply to mocked and proxied responses as before, with four exceptions. A preflight for a request the control plane serves is answered by MockServer under controlPlaneCorsAllowOrigin, not by an expectation (including a respondBeforeBody one) or corsAllowOrigin; a respondBeforeBody expectation no longer answers a request the control plane serves; a browser request from a page controlPlaneCorsAllowOrigin does not allow is refused with 403 even where a mock, not the control plane, would have answered it, for these requests: GET or DELETE on /loadScenario/start or /loadScenario/stop; PUT /pact; GET or DELETE on any deeper path under /loadScenario/<name>/ other than GET /loadScenario/<name>/report; GET, PUT or DELETE under /chaosExperiment/profiles/<name>/; POST under /chaosExperiment/apply/<name>/; and any method on a /mockserver or /_mockserver_ path that is not a route; and an unmatched preflight for another method on a legacy path (for example POST /graphql to a mocked GraphQL API) allows only the requested method. Clients that are not browsers are unaffected; the Java client no longer sends an Origin header on its WebSockets. Action needed only if a browser page on another host calls MockServer's API, the dashboard targets a MockServer on another host, a reverse proxy in front of the dashboard rewrites the Host header, or you set corsAllowOrigin so a page could call the API: set controlPlaneCorsAllowOrigin on that MockServer to the page's origin, for example -Dmockserver.controlPlaneCorsAllowOrigin=https://app.example.com. This does not stop a site visited by a browser that uses MockServer as its proxy, because the page is then same-origin with MockServer; turn on control-plane authentication if untrusted pages may run in a browser that can reach MockServer
  • A DNS-rebinding page can no longer use MockServer's API or WebSockets by default (BREAKING for a dashboard opened at a public-looking name; see upgrading to 9). A malicious site can point its own host name at MockServer, so its page becomes same-origin with MockServer and passed the "pages MockServer serves itself" rule. That rule now applies only when MockServer is reached by localhost, an IP address, a single-label name (such as a Docker or Compose service name like mockserver, or a Kubernetes short service name), a name ending in .svc, .svc.cluster.local or .internal (the top-level domain reserved for private use, which covers host.docker.internal and *.ec2.internal), or a name ending in a suffix listed in the new controlPlaneTrustedHostSuffixes property (for clusters with a custom domain, for example corp.example). This applies even when controlPlaneCorsAllowOrigin is set: listing some origins never restores same-host trust for other names. The trusted-name check ignores case and one trailing dot, and the page's origin must still match the Host exactly, port included; look-alikes such as localhost.evil.com, svc.evil.com or internal.evil.com, and non-ASCII names, do not count. A page on any other name, for example http://evil.example.com:1080 sent with Host: evil.example.com:1080, now gets 403 on MockServer's API and WebSockets. Action needed only if you open the dashboard, or a page MockServer serves, at a multi-label name such as https://mockserver.example.com: list that origin in controlPlaneCorsAllowOrigin, or add a private suffix to controlPlaneTrustedHostSuffixes. Either opts that name back in, including to a rebinding attack on it. Like controlPlaneCorsAllowOrigin, controlPlaneTrustedHostSuffixes can only be set at startup; corp.example, .corp.example and *.corp.example mean the same, a public suffix such as com, co.uk or github.io, under which anyone can register a name, is rejected with an error at startup and trusts nothing, and an entry it cannot use is logged as a warning. This stops rebinding by an ordinary web site; someone who controls how the victim's machine resolves names (a hostile Wi-Fi resolver, LLMNR or mDNS spoofing on the local network, a DNS search domain, or the cluster DNS) can still rebind any trusted name except an IP address or localhost, so turn on control-plane authentication if that is in your threat model. A request whose Host headers disagree (for example an HTTP/2 :authority and a different host header) is never treated as same-host
  • The callback WebSocket now requires control-plane authentication when it is turned on, as the dashboard's WebSocket already did. The Java client now sends its control-plane JWT (or its request override's Authorization header) on the callback and breakpoint WebSockets, and the other clients send their bearer token (see above). With mTLS control-plane authentication the WebSocket now needs the client certificate too: the Java and Rust clients present it, and the Node, Python, Go and .NET clients now do too (see below). Upgrade the clients with the server: older ones send no token or certificate on the WebSocket, so with control-plane authentication on their callbacks and breakpoints are refused. The dashboard, like the Node client in a browser, needs mTLS for breakpoints when control-plane authentication is on
  • Node, Python, Go and .NET clients: the callback and breakpoint WebSocket now presents the client's configured mTLS client certificate and trusts its configured CA certificate, as control-plane HTTP calls already did, so callbacks and breakpoints keep working when MockServer requires mTLS control-plane authentication on that WebSocket. Before, the WebSocket presented no client certificate, and the Go and .NET clients also ignored a configured CA certificate on it. The Go client uses the TLS settings of an http.Client passed with WithHTTPClient; the .NET client applies its TLS settings only when they were set through MockServerClient.Builder(...).WithTls(...), not when you pass your own HttpClient. The Rust client already presents its client_cert_pem certificate on its secure callback WebSocket; the Ruby client has no client certificate option and the PHP client has no callback WebSocket, so neither changes
  • A callback WebSocket that registers with a client id (X-CLIENT-REGISTRATION-ID) already in use no longer silently takes over that client's object callbacks and breakpoints. With control-plane authentication on, the same caller takes over at once: the same JWT issuer and subject, the same OIDC principal, or the same mTLS client certificate (compared by its SHA-256 fingerprint, so a renewed certificate counts as another caller). Otherwise, MockServer pings the registered connection and refuses the new one if it answers within 2 seconds, with 403 Forbidden when both are authenticated as different callers and 409 Conflict otherwise; if it does not answer, or has closed, the new connection takes over and the old one is closed, so a client still reconnects after its connection drops without MockServer noticing. A client answers only while it is reading its WebSocket, so a live client that spends more than 2 seconds in a callback or breakpoint handler does not answer, and can be taken over by another connection: by anyone with authentication off, or by any authenticated caller with it on. A client whose credential changed, such as a renewed certificate, can therefore still replace its own dropped connection. Only one connection at a time can take a client id over; another trying at the same moment gets 409. The Java client now flushes its pong at once, so a Java connection that is not inside a long callback answers the check
Added
  • A capture-and-replay example for a remote MCP server, in every client language (Java, Node, Python, Ruby, Go, .NET, Rust, PHP, curl, with shared JSON under examples/json/parallel_search_mcp): MockServer records an MCP client's calls to the live Parallel Search MCP server, then replays them offline from expectations that match on the JSON-RPC method and echo the request id
  • Rust client: an HttpResponse can now carry any response body the server accepts, not only a string. New builders binary_body(bytes, content_type) (a BINARY body, base64-encoded on the wire and served back byte for byte), json_body(..), file_body(..) and body_value(Body) sit beside body(..), and typed_body() reads any body back as a Body (for a retrieved expectation, in the shape the server returned). HttpRequest::binary_data(bytes) builds a binary (non-HTTP) request matcher for use with respond_with_binary, and binary_data_bytes() reads it back. Body::with_content_type(..) now sets the content type on JSON, BINARY, STRING and XML bodies as well as FILE bodies, and wraps an inline JSON object (one with no type, or a type that is not a MockServer body type, such as GeoJSON's FeatureCollection) as a typed JSON body so the content type is not added to the JSON payload; other body types are left unchanged. HttpResponse.body keeps its Option<String> type and a string body is unchanged on the wire; other body shapes are kept in HttpResponse.extra["body"]
  • Dashboard: each workspace can target its own MockServer. The Set MockServer button on a workspace tab takes a host, port, https and base path, checks the server answers the dashboard (and explains why not: unreachable, CORS refused, control-plane authentication, a wrong base path, or an http server from an https dashboard), and every tab then shows the server it targets. Every view, action and live update in a workspace goes to its server; switching to a workspace on another server clears what was shown from the previous one. Breakpoints set on a server stay active while any workspace targets it, and are removed when the last workspace targeting it is closed or pointed elsewhere
  • Dashboard: the Verify view's response matcher has a Status range (a class such as 4XX, or one comparison such as >= 400), and its quick scope now accepts status:, for example status:>=500 or status:>=400 status:<500 (filled in as 4XX). A range that needs two comparisons, such as status:>=400 status:<450, is refused rather than widened
  • Rule 8 in the always-on check-false-green-guards.sh CI gate fails the build when a test asserts an HTTP status loosely, for example not(is(401)), anyOf(is(200), is(404)), status < 300, .not.toBe(404) or status in (200, 404), because such an assertion also passes on a 500. It covers Java, Kotlin, JavaScript/TypeScript, Python and Go test sources; a genuinely tolerant assertion carries a status-tolerance-ok: <reason> comment. The existing tolerant assertions were pinned to their exact status. See docs/operations/false-green-guards.md
  • Security scanning: a new zizmor GitHub Actions workflow audits .github/ (workflows, composite action and dependabot.yml) and reports its findings as code scanning alerts under the category zizmor, and CodeQL now also scans C#, Go, Rust and the GitHub Actions workflows, with the security-extended query suite for every language. See docs/operations/security.md
  • Security scanning: a new agent-config-scan GitHub Actions workflow checks the AI agent configuration (.opencode/, .claude/, AGENTS.md, CLAUDE.md, opencode.json(c), .mcp.json, SKILL.md and similar files, at any depth) with deterministic rules for hidden or bidirectional Unicode, permission drift against a committed baseline (including settings env), hooks, plugins and marketplaces not on an allowlist, unpinned or unlisted MCP servers, credential patterns, symbolic links, imported files and CODEOWNERS coverage. It is closed-world and fails closed: files under an AI-tool configuration root that no check claims, configuration keys no check handles, case variants of known names, YAML frontmatter outside a small subset, JSON that fails strict parsing, imports it cannot follow and launchers it does not recognise are all findings. Findings are code scanning alerts under the category agent-config and fail the job. The scanner is a standard-library Python script with a committed policy file, both CODEOWNED. See docs/operations/security.md
  • Security scanning: the agent-config scan now also checks VS Code workspace files (*.code-workspace) and dev container configuration (.devcontainer/devcontainer.json, .devcontainer/**/devcontainer.json and .devcontainer.json, at any depth). A workspace's settings, and a dev container's customizations.vscode.settings, get the same closed-world checks as .vscode/settings.json, including MCP servers and auto-approve settings. A workspace folder must be a directory of files in the repository. Dev container lifecycle commands such as postCreateCommand and initializeCommand must be on their own allowlist and use a recognised launcher. The container image must be pinned by @sha256: digest or allowlisted. Any other key that is not listed as inert, such as features, runArgs, mounts, containerEnv, tasks or launch, is a finding. See docs/operations/security.md
Changed
  • MockServer is not published to Chocolatey, SDKMAN!, winget, Scoop, asdf/mise or a mock-server Homebrew tap; install the CLI with brew install mockserver (from homebrew-core) on macOS or Linux, or from the windows-x86_64.zip on the GitHub Release on Windows; Docker and the executable JAR work everywhere
  • Java client: chaining withBeforeAction(...) or withAfterAction(...) now adds each action to the expectation, so .withAfterAction(a).withAfterAction(b) runs both a and b. Before, each call replaced the list and only the last action ran. withBeforeActions(...) and withAfterActions(...) still replace the whole list. The same singular methods are now on Expectation too
  • ClassGraph upgraded from 4.8.198 to 5.0.0. If you use the regular (not -no-dependencies) artifacts, three new transitive jars arrive with it (classgraph-base, classgraph-vfs, classgraph-classpath), and a project that pins classgraph to 4.x must stop doing so, because 5.x is not binary-compatible; for the same reason, another library in your build that uses the 4.x API can break if Maven resolves it to 5.0.0, so use the -no-dependencies artifacts there
  • Dashboard: the generated Rust code for a response with a FILE, BINARY, typed JSON or other object body now sets it with .body_value(Body::object(..)), and a JSON array or scalar body with .json_body(..), instead of inserting it into response.extra
  • At the INFO log level (the default) MockServer now keeps more log entries waiting to be recorded before it starts dropping them. The cap on entries still waiting to be logged at INFO is now a seventh of the heap ceiling, as it already was at WARN, instead of a twelfth; DEBUG and TRACE keep a twelfth. Under heavy load with large bodies fewer log events are dropped, so fewer upper-bound verifications fail because evidence was dropped. On the JSON and proxy traffic we measured, the whole event log should still stay under about a fifth of the heap
  • At the INFO log level (the default) MockServer now keeps about 40% fewer past log entries by default: the event-log size budget (maxEventLogSizeInBytes) is a twentieth of the heap ceiling, as it already was at WARN, instead of a twelfth. When a server is close to the most traffic it can log, this means the garbage collector does less work and the slowest responses are faster; with G1 the 99th-percentile response time was up to about a third lower in our tests. verify(), retrieve and the dashboard see fewer of the oldest requests, and an upper-bound verification fails sooner once they are evicted. To keep the previous amount of history, set maxEventLogSizeInBytes to a twelfth of your heap. DEBUG and TRACE are unchanged
  • Logging a message that quotes a request, response or expectation uses less CPU, at every log level: the quoted text is now indented line by line with a simple loop instead of a regular expression. In a micro-benchmark this renders a typical message 25–37% faster and allocates 35–46% less; the logged text is unchanged, byte for byte. Whether it raises the request rate at which a busy server logging at INFO starts dropping log events has not been measured yet
  • Logging at INFO (or more verbose) uses less CPU when nothing prints the log, for example with disableSystemOut set and no other log handler configured, or when no SLF4J logging provider is on the classpath. MockServer no longer builds the text of each log message just to throw it away; retrieving the logs, the dashboard and verification failure messages still show exactly the same text, built when they ask for it. When the log is printed, a message that quotes a request, response or expectation is now built in one pass instead of first building each quoted part separately: in a micro-benchmark that is 5–11% faster and allocates 21–40% less, and the printed text is unchanged, byte for byte. Whether this raises the request rate at which a busy server logging at INFO starts dropping log events has not been measured yet
Fixed
  • The /mockserver/metrics handler no longer answers 500 to a request that carries no keep-alive flag, such as one built in code and passed to the handler directly; it now treats a missing flag as "close" and sets the 200 status explicitly. Requests decoded from the wire always carry the flag, so a server scraped over HTTP was not affected
  • Dashboard: with the dashboard open in two browser tabs, a reload no longer gives one workspace another workspace's view and search terms; each tab now keeps the others' workspace changes instead of overwriting them
  • MCP and A2A mocks built with the Java, Node, Python, Ruby, Go, PHP, .NET or Rust client builders now return text containing a backslash before $ or # exactly as written; previously a\$b was returned as a$b and a\#b as a#b
  • Java, Node and Python clients: importing a WireMock, Mountebank or Mockoon document (importDocument / import_document), including one whose format is auto-detected, now returns the imported expectations. The server had already added them, but its reply also carries warnings, which the clients could not read: Java threw IllegalArgumentException, Node resolved the whole reply object instead of an array, and Python returned an empty list. Each client now returns the expectations and logs the warnings (Java at WARN, Node with console.warn, Python with logging at WARNING). A reply object without an expectations list (for example an error from a gateway) is now an error rather than an empty result. These methods also now refuse format=recording before sending anything. Before, the server would load the recorded request/response pairs into its request log and the client returned them as if they were added expectations; to re-import recorded traffic, send PUT /mockserver/import?format=recording directly. The Ruby, Go, .NET, Rust and PHP clients only import HAR and Postman documents, whose reply is a plain list, so they were not affected
  • Java, Python, Ruby and Go clients: starting a chaos experiment no longer reports a 403 as "chaos is disabled". No server setting disables chaos experiments; a 403 means control-plane authorization rejected the request, and the error now says so. In Go, StartChaosExperiment and SetServiceChaos no longer return FeatureDisabledError for a 403. An invalid experiment is still rejected with 400
  • Drift alert webhook, LLM semantic drift analysis and the p95 performance drift threshold no longer carry over from a stopped MockServer to the next one started in the same JVM (for example one ClientAndServer or JUnit rule per test). Before, a server started with driftAlertWebhookEnabled or driftSemanticAnalysisEnabled left them switched on after it stopped, so later servers kept posting its webhook and calling its LLM even with both flags off. These three settings are shared by the whole JVM: the most recently started server that turns any of them on decides all three, a server that turns none on leaves them unchanged, and when the deciding server stops they revert to the previous such server still running, or are switched off
  • Rust client: retrieving expectations whose httpResponse (or template responseOverride) body is not a plain string, such as a BINARY, typed JSON, FILE or XML body or an inline JSON object, no longer fails with "invalid type: map, expected a string"; the body is read back and sent again unchanged
  • Rust client: a JSON array or scalar body read back from the server (request or response) is now a typed JSON body rather than a plain string, so sending it back no longer turns a JSON body into a string body
  • Go client: a DNS MX record with priority 0 (for example MXRecord(name, 0, exchange)) is now served with preference 0. Before, the client left a zero priority out of the expectation and the server answered with its default preference of 10. A DnsRecord with Type: "MX" now always sends its Priority, so an MX record built as a struct literal without Priority is now served with preference 0 rather than 10
  • Rust client: close_breakpoint_websocket() no longer hangs. While the callback WebSocket was idle, its background reader held the socket lock while waiting for the next message, so the close call waited on that lock forever. This WebSocket is opened by mock_with_callback and by breakpoints. The call now returns at once and closes the connection
  • HTTP/3: large downloads with curl --http3 built on OpenSSL's QUIC support (as shipped by Debian and Ubuntu) stall far less often on a Linux host left at the usual net.core.rmem_max. MockServer now reads more of the client's packets at a time, so fewer of the client's acknowledgements and flow-control updates are dropped. This cut stalls from 14 to 2 in 90 downloads of 20 MiB at a stock buffer. Raising net.core.rmem_max to 4194304 on the host prevented every stall in our tests (none in 180 downloads)
  • HTTP/3 on Linux: the HTTP/3 port now uses the native epoll transport, like the TCP ports, when useNativeTransport is on (the default). It reads the client's packets in batches and sends its own in batches (GSO), so large downloads with curl --http3 built on OpenSSL no longer stalled at the usual net.core.rmem_max: none of 122 downloads of 20 MiB stalled, against 11 with the previous transport, and downloads ran about 55% faster on about 25% less CPU. Where the network path refuses batched sends (some VPN or rootless-container network devices), MockServer logs one warning and sends packets one at a time from then on. macOS and Windows, and Linux with useNativeTransport=false, keep the previous transport
  • Java, Go, Node, Python, Ruby, PHP, .NET and Rust clients: an MCP mock whose tool name, resource URI or prompt name contains a backslash now matches tools/call, resources/read and prompts/get requests for it. Before, the backslash was not escaped in the request matcher, so a name such as a\b never matched, and one ending in a backslash or containing \' made the matcher invalid
  • Rust client: the callback WebSocket used by mock_with_callback and breakpoints now works against a MockServer reached over HTTPS (secure(true)). Before, it could not open a wss connection at all. It now uses the same TLS settings as the client's HTTP calls: the platform trust roots, a CA added with ca_cert_pem or ca_cert_pem_path, tls_verify(false), and the client_cert_pem client certificate
  • .NET client: disposing the client no longer hangs when MockServer stops answering on the callback WebSocket. If the server did not reply to the close handshake while a callback handler was running, or just after the WebSocket connected, Dispose waited forever. It now waits at most 5 seconds and then drops the connection

v9.0.0

This release delivers a sustained performance and memory programme alongside data-integrity fixes under load, plus a large set of LLM-provider correctness fixes, dashboard improvements, and client-library parity work. Headline numbers from the single-load-generator benchmark on the same six-core rig (builds 420 and 464): the healthy ceiling rises from 39,033 to 57,149 req/s and the peak from 43,671 to 59,905 req/s, with p95 at 32,000 req/s falling from 56.6 ms to 0.44 ms on the new ZGC default in the Docker images; event-log retained heap at 20,000 entries falls from 429 MB to 61 MB; Docker image download shrinks ~23%; and instance shutdown drops from ~107 ms to near zero. 22 BREAKING changes affect HTTP/3, DNS mocking, binary (non-HTTP) proxying and binary expectation matching while proxying, template-failure response codes, maxLoggedMatchFailures's default, initialisation-file id conflicts, the Node client's TypeScript typings and exports map, LOG_ENTRIES log output, control-plane error handling and mTLS, the Rust client's enums, verification-failure wording, response statusCode validation, AsyncAPI and response/OpenAPI verification, GenAI trace span names, the Node client's recorded-request typings, and the Go client/Testcontainers module's /v9 module paths — see Upgrading from 8.0.0 and the BREAKING entries under Changed. For a per-change migration list, see the upgrade guide at https://www.mock-server.com/mock_server/upgrading_to_9.html.

Metric Before After
Throughput, healthy ceiling / peak (req/s, 6 cores) 39,033 / 43,671 57,149 / 59,905
p95 latency at 32,000 / 48,000 req/s offered 56.6 ms / 82.4 ms 0.44 ms / 12.0 ms
Event-log heap (20,000 entries) 429 MB 61 MB
Docker image download (linux/arm64, standard image) ~167 MB ~129 MB
Instance shutdown time ~107 ms ~0 ms

"Before" is perf build 420 (commit d568695, 2026-09-24, JDK 17.0.20, G1 collector), measured nine days after the 8.0.0 tag and already carrying some of this release's own work, since 8.0.0 itself was never measured on this rig. "After" is perf build 464 (commit efdc522, 2026-09-27, JDK 25, ZGC), the performance page's single-load-generator figures; the page's headline is now a different, multi-generator measurement (see Changed). Both are comparable with each other but not with any benchmark published after this release.

Upgrading from 8.0.0
  • Go client and Go Testcontainers module users: both modules move to /v9 module paths (the Go module proxy refused their 8.0.0 tags) — run go get github.com/mock-server/mockserver-monorepo/mockserver-client-go/v9 (and the Testcontainers module's /v9 path if you use it) and change your
    imports from the /v7/unsuffixed paths to the /v9 ones; see Changed.
  • HTTP/3 users (http3Port set): the QUIC native library now ships separately — use the -jar-with-dependencies-http3 jar or the mockserver/mockserver:<version>-http3 Docker image (latest-http3, snapshot-http3; Helm: image.variant=http3), and a server that cannot serve HTTP/3 (missing
    native, or the port unusable) now refuses to start instead of silently falling back.
  • DNS mocking users (dnsEnabled=true): MockServer now refuses to start if its dnsPort cannot be bound, instead of logging a warning and running without DNS. Leave dnsPort at its default of 0 to let the OS pick a free port, or set dnsEnabled=false.
  • TypeScript users of mockserver-client/llm: the deep-import typings no longer declare a default export; import from mockserver-client itself, use import llm = require(...)/import * as llm from ..., or turn on esModuleInterop (see Changed).
  • Binary (non-HTTP) proxy users: a client connection now always keeps one upstream connection for its whole life — forwarding one message per upstream connection is removed, along with its forwardBinaryRequestsWithoutWaitingForResponse setting (a value still set is ignored, with a warning); remove it from your configuration.
  • Anyone reading a request or response body from message/arguments of LOG_ENTRIES output (including the MCP retrieve_logs/raw_retrieve tools): read it from the entry's httpRequest/httpResponse instead — message/arguments now refer to that request/response by a short form
    such as "POST /orders" or "201".
  • Anyone treating a control-plane 400 as any failure, or catching the Java client's IllegalArgumentException for non-input failures: an unexpected server-side fault is now a 500 with a generic message and correlation id; 400 is now reserved for requests MockServer genuinely rejects. The
    Java client's retrieveLogsByCorrelationId and retrieveActiveExpectations(requestDefinition, format) now throw instead of returning the error's text.
  • Anyone importing a path inside mockserver-client/mockserver-node other than the package or a documented module: both packages now have an exports map, so an unlisted path (.d.ts files, tasks/mockServer.js, etc.) now fails to resolve — resolve <package>/package.json and join a file's
    path to that directory if you need one by location.
  • Control-plane mTLS, Java client: the client now trusts only controlPlaneTLSMutualAuthenticationCAChain for MockServer's certificate; add MockServer's own CA to that chain if it signs the server certificate.
  • Control-plane mTLS, server: MockServer now accepts only client certificates signed by controlPlaneTLSMutualAuthenticationCAChain; add MockServer's own CA to the chain if it signs your client certificates (or use your own CA if it's the bundled default), and MockServer now refuses to start with
    mTLS required, no chain set, and the bundled default CA — set the chain or configure your own CA with certificateAuthorityCertificate/certificateAuthorityPrivateKey.
  • Rust client users who match on its enums: every public enum is now #[non_exhaustive] (add a wildcard arm), and server-sent enums (ResponseMode, BinaryUpstream, CrossProtocolTrigger, the Load* enums) gain an Unknown variant that cannot be sent back; LoadThresholdMetric also gains
    CheckFailureRate.
  • Anyone asserting the exact text of an over-count verification failure (never(), exactly, atMost, between, verifyZeroInteractions): the message now reads "found … but should have been found …" instead of "not found".
  • Anyone with a response statusCode of 0 or outside 100–999 (most likely from a blank 8.0.0 dashboard status field): it is now rejected at creation with a 400, or skipped with a WARN (naming it) when loaded from an initialization/persisted-expectations file, with the file backed up first.
  • AsyncAPI verification users (PUT /mockserver/asyncapi/verify, or the Java client's verifyAsyncMessage): put atLeast/atMost/exactly inside count, not at the top level — the endpoint now answers 400 for unknown fields instead of ignoring them.
  • Anyone verifying a response body with a regex, JSON path, JSON Schema, XPath, XML Schema, form, multipart, fuzzy, GraphQL, JSON-RPC, WASM or allOf matcher: that matcher is now actually applied, where it was previously ignored (verification passed whenever any recorded response had a JSON object
    body).
  • Anyone verifying requests with an OpenAPI matcher: only requests for the matched operationId now count, instead of every recorded request (verifySequence was unaffected).
  • Anyone selecting LLM completions by gen_ai.operation.name == "chat": Gemini calls are now generate_content, and prompt-string completions (legacy /v1/completions, Mistral FIM, Ollama /api/generate, prompt-based Bedrock InvokeModel models) are now text_completion — update to match
    chat, generate_content and text_completion together.
  • TypeScript users of the Node client's retrieveRecordedRequests/retrieveRecordedRequestsById: now typed Promise<RequestDefinition[]> instead of Promise<HttpResponse[]>; narrow each result to HttpRequest before reading HTTP-specific fields.
  • Anyone proxying binary (non-HTTP) traffic while binary expectations are registered on the same MockServer: a message matching a binary expectation is now answered by it and not forwarded, by default (matchBinaryExpectationsWhenProxying); set it to false to always forward as in 8.0.0.
  • Anyone relying on every "didn't match" comparison being logged: the new maxLoggedMatchFailures property now defaults to 50, so at INFO only the first 50 "didn't match" entries per request are logged; set maxLoggedMatchFailures=-1 to keep logging every comparison.
  • Anyone with expectations in an initializationJsonPath file whose ids repeat: the file now keeps the last expectation for each repeated id, not the first; remove the repeated ids, or put the one you want last.
  • Anyone relying on a failed template answering 404/502: a response/forward template that fails, or whose output isn't valid, now answers 500 by default (templateFailureAsServerError); set it to false to keep the 8.0.0 answers.

Also worth checking, though none is a breaking change: if you override the standard Docker images' ENTRYPOINT or copy the jar out of them, /mockserver-netty-jar-with-dependencies.jar is now split into /mockserver.jar and /mockserver-deps.jar — use -cp /mockserver.jar:/mockserver-deps.jar:/libs/* (java -jar /mockserver.jar still runs, but without the CDS start-up archive, so it starts more slowly); with forwardProxyBlockPrivateNetworks on, a request that matches no expectation and whose Host header names MockServer itself by a name it doesn't recognise as its own (a Docker Compose or Kubernetes service name, for example) now gets 502 Bad Gateway instead of 404 Not Found, because that name resolves to a private address — add the name to noProxyHosts, or set attemptToProxyIfNoMatchingExpectation=false if MockServer isn't used as a proxy, to get the 404 back; and if you set localBoundIP, it now also keeps the DNS and HTTP/3 ports on that address — a DNS query or HTTP/3 request sent to another address of the host is no longer answered, so send them to the localBoundIP address, or leave localBoundIP unset.

Security
  • Velocity templates now have a time limit (mockserver.velocityTemplateExecutionTimeout, default 5000 ms, same as JavaScript templates). A runaway #foreach, recursive macro or chained #define block could previously hold a template thread until restart; it now fails with a timeout error at
    its next loop iteration, macro call or #parse/#evaluate. A single slow Java call inside a template is not interrupted, and the limit does not cap memory. Set the property to 0 to remove it.
  • Netty upgraded from 4.2.17.Final to 4.2.19.Final, and BouncyCastle from 1.85 to 1.86, a security release whose advisories in the parts of Netty MockServer uses cover HTTP/1.1 request/response smuggling (including improper CRLF neutralization) and an origin validation error, unbounded
    resource consumption in the HTTP/1.1, HTTP/2, HTTP/3 and DNS codecs and the base codec, input misinterpretation in the SOCKS codec, and an SNI routing bypass and an improper access control in the handler module. Two visible changes: an HTTP/2 client whose SETTINGS_INITIAL_WINDOW_SIZE change would
    overflow a stream's flow-control window now has its connection closed with FLOW_CONTROL_ERROR, as RFC 9113 requires, instead of having that one stream reset; and in a multipart/form-data request, a file part whose Content-Type is not a plausible media type (for example text) is now read as
    application/octet-stream, so a partContentTypes matcher for such a part must name application/octet-stream. Netty 4.2.18 also tightened its HTTP/2 frame decoder to validate header values as well as names; unvalidated inbound frames (a leading space, an embedded DEL 0x7F, other control
    characters in a header value) are a shape MockServer is used to record, so inbound frame validation is now disabled on the multiplex codec, keeping that leniency for HTTP/2 requests on the multiplex path (this also disables inbound header-name validation there, since Netty folds both under one
    flag); outbound names and HTTP/2-forbidden connection-specific headers (Connection, Transfer-Encoding) remain validated.
  • The binary launchers (Node, Python, Ruby, Go, Rust, .NET, PHP) no longer start MockServer through a shell, or no longer let a shell misread their arguments, on Windows; the Node launcher never uses a shell at all.
    • The Node launcher no longer starts MockServer through a shell. On Windows, runBinary and the mockserver command ran the bundle's mockserver.bat under cmd.exe, so %NAME% in an argument or in the cache directory path was replaced with an environment variable's value, and a line break cut
      the command short. On every platform they now run the bundle's own Java runtime directly, with the JVM options the launcher script applies, and pass each argument separately with no shell in between, so no shell can read %, &, $ or ; in an argument or path as a variable or a command.
      MOCKSERVER_JAVA_OPTS is split on whitespace with quoted text kept together, so -Dx="a b" sets x to a b on Linux and macOS too. A shell option passed in spawnOptions is now ignored.
    • The Python client's binary launcher no longer starts MockServer through a shell on Windows. start() ran the mockserver.bat launcher with shell=True, so cmd.exe could treat characters in the launcher path or in extra_args as commands rather than text. It now runs cmd.exe itself with
      every argument quoted, so characters such as &, | and ^ reach MockServer unchanged. start() raises ValueError if the launcher path or an extra_args entry contains a double quote, a line break or NUL, or if together they contain more than one %, which cmd.exe would expand as an
      environment variable. Linux and macOS are unchanged and still use no shell.
    • The Ruby client's binary launcher now quotes every argument it passes to cmd.exe on Windows. BinaryLauncher.start let Ruby quote the arguments for cmd.exe, which follows different rules, so characters in the launcher path or in extra_args could be read as commands. It now builds the
      cmd.exe command line itself and raises MockServer::Error if the launcher path or an extra_args entry contains ", <, >, |, &, a line break or NUL, or if together they contain more than one %. On Windows it needs a native Ruby (mswin or mingw, such as RubyInstaller). On Linux and
      macOS it now never uses a shell, even with no arguments.
    • The Go, Rust and .NET clients' binary launchers now quote the launcher path they pass to cmd.exe on Windows. They ran mockserver.bat through cmd.exe with the standard argument quoting, which cmd.exe does not follow, so characters in the launcher path (from MOCKSERVER_BINARY_CACHE or the
      user's cache folder) could be read as commands. Each now builds the cmd.exe command line itself, quoting every part, and refuses a launcher path that contains a double quote, a line break or NUL, or more than one %.
    • The PHP client's binary launcher now quotes every argument it passes to cmd.exe on Windows. BinaryLauncher::start() let PHP quote the launcher path and $extraArgs for cmd.exe, which follows different rules, so characters in them could be read as commands. It now builds the cmd.exe
      command line itself and throws BinaryInstallException if the launcher path or an extra argument contains a double quote, a line break or NUL, or if together they contain more than one %.
    • A long MOCKSERVER_BINARY_BASE_URL made of repeated / no longer takes the Node launcher seconds to process.
  • Dependency security upgrades: Jackson 2.22.2 → 2.22.3 (three Snyk-reported vulnerabilities); dashboard dependency KaTeX 0.16.47 → 0.18 (low-severity prototype-pollution bypass of its trust restrictions); dashboard dependency dompurify resolves to 3.4.16 (DOM XSS via IN_PLACE sanitize with a
    node-removing hook, Dependabot alert 593); dashboard npm overrides pins lodash-es to 4.18.1 (_.template/_.unset/_.omit advisories; the affected functions are tree-shaken from the built dashboard anyway); the Node Testcontainers module's own development lockfile resolves newer undici,
    brace-expansion and @grpc/grpc-js (Dependabot alerts 581, 582, 584, 586, 591, 592 — the published package is unaffected, since it ships only dist).
  • A zstd request or streamed compressed upstream response can no longer exhaust MockServer's memory. A zstd frame declares its decompressed size, and MockServer previously allocated that much up front (17 bytes claiming 1.5 GB threw OutOfMemoryError on a 256 MB heap) — now decompressed in
    64 KB pieces, with maxRequestBodySize/maxResponseBodySize enforced as for any other coding.
    • A streamed response (SSE and similar; on by default) was never body-size-limited at all, so a single network read's worth of compressed bytes could decompress to gigabytes (a 256 MB zstd stream, 8 KB on the wire, exhausted a 256 MB heap); such a stream is now capped at
      maxResponseBodySize (50 MB by default) of data waiting to be written to the client, ending incomplete past that point rather than looking finished.
    • A legitimate slow client is unaffected: MockServer reads more of the upstream only once the client has taken nearly all of what is waiting, so a slow client holds back the upstream rather than filling memory, and only a response that decompresses to nearly the limit or more from a single
      network read is stopped; the wait doesn't count towards streamIdleTimeoutSeconds (which now measures only an upstream that sends nothing while MockServer is reading it — a client that pauses keeps its stream, with at most the limit waiting for it, until it reads on, disconnects or takes
      nothing for responseWriteStallTimeoutMillis).
    • A stream that times out, or whose upstream closes, fails or sends invalid chunk framing part-way through, now ends incomplete, where before it ended with a normal final chunk and looked complete; a stream whose upstream simply ends by closing its connection still completes normally. When the
      client goes away, MockServer now also closes the upstream, where before it kept reading an endless stream forever.
    • A stream through a CONNECT tunnel is limited the same way, but by maxRequestBodySize (10 MB by default) — the limit that tunnel already applied to a response it collected whole, not maxResponseBodySize; through such a tunnel this backpressure is new for a streamed response, which before
      was queued in memory as fast as it arrived.
  • A chunked HTTP/1.1 request can no longer make MockServer buffer an endless chunk-size line. Each chunk of a chunked request body starts with a line giving its size, which may carry extra parameters (chunk extensions), and the body may end with trailer headers. MockServer limited that line
    only by maxInitialLineLength and the trailers only by maxHeaderSize, which were both unlimited by default, so MockServer kept buffering a line its client never ended, up to 2 GB or until its network memory ran out. MockServer now answers 400 and closes the connection once more than 8 KB of
    one chunk-size line, or of the final 0 line and its trailers counted together, is waiting to be read, on requests sent to it directly and through a CONNECT or SOCKS tunnel. The limit is fixed: real uploads use about 100 bytes per line, and 8 KB is what Tomcat allows. Anything within the
    limit, the request line, headers and body sizes are unaffected, and a lower maxInitialLineLength or maxHeaderSize still applies. HTTP/2 and HTTP/3 have no chunk-size lines.
  • The request line and headers of an HTTP/1.1 request are now limited by default, and a request MockServer cannot read is refused instead of answered. maxInitialLineLength (64 KB) and maxHeaderSize (256 KB) were previously unlimited; set either to 2147483647 to remove the limit. A longer
    request line now gets 414, larger headers 431, and any other unreadable request (an invalid header, a chunked body with an invalid chunk size) 400, with the connection then closed — where before such a request was logged as an ERROR and matched/answered from whatever had been read (a request
    line over the limit, for example, as GET /bad-request). This also applies through CONNECT/SOCKS tunnels. A request is also now unreadable, and

[!IMPORTANT]
✂ PR body was truncated to here.


Configuration

📅 Schedule: (in timezone UTC)

  • Branch creation
    • "before 6am on monday"
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.

♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovate
renovate Bot requested a review from a team as a code owner October 21, 2026 03:48
@coderabbitai

coderabbitai Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: Repository: kinde-oss/kinde-java-sdk/.coderabbit.yaml
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 76efb67c-f0c2-4a2d-a63d-7f15101c5360

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Oct 21, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@renovate
renovate Bot force-pushed the renovate/org.mock-server-mockserver-netty-9.x branch 2 times, most recently from 4b2fc03 to 1953e25 Compare October 21, 2026 10:00
@dtoxvanilla1991 dtoxvanilla1991 self-assigned this Oct 22, 2026
@dtoxvanilla1991

Copy link
Copy Markdown
Contributor

@copilot review dependencies latest fresh official only documentation that are being updated here in PR. Check and validate for any possible migration required and if so, provide detailed minimally viable changes required in a structured actionable list. If breaking changes are in place, clearly detail them. Check for CI issues and if found, define solutions briefly at the end of the list. If there are no changes to be made, leave only 1-2 sentence short overview and state that it is safe to merge. No need for detailed specs.

Copilot AI commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

@copilot review dependencies latest fresh official only documentation that are being updated here in PR. Check and validate for any possible migration required and if so, provide detailed minimally vi...

MockServer 9.x has breaking changes for TLS-configured Java clients and removed binary-forwarding APIs, but this repository has no MockServer usages and the dependency is test-scoped, so no migration is needed; see the official migration guide. The Maven build/tests and other completed PR checks passed with no CI failures, so this is safe to merge.

@dtoxvanilla1991
dtoxvanilla1991 merged commit c6dd195 into main Oct 22, 2026
6 checks passed
@renovate
renovate Bot deleted the renovate/org.mock-server-mockserver-netty-9.x branch October 22, 2026 00:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants