Skip to content

chore(deps): update dependency org.mock-server:mockserver-netty-no-dependencies to v8 - #2475

Merged
renovate[bot] merged 1 commit into
mainfrom
renovate/org.mock-server-mockserver-netty-no-dependencies-8.x
Sep 15, 2026
Merged

renovate[bot] merged 1 commit into
mainfrom
renovate/org.mock-server-mockserver-netty-no-dependencies-8.x

Conversation

@renovate

@renovate renovate Bot commented Sep 15, 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-no-dependencies (source) 7.6.08.0.0 age confidence

Release Notes

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

v8.0.0

Added
  • Test port allocation can opt into a fixed port band instead of the OS ephemeral range.
    org.mockserver.socket.PortFactory normally finds a free port with bind(0), which draws from the same
    ephemeral range (on macOS net.inet.ip.portrange.hifirst..hilast, typically 49152-65535) that every other
    bind(0) on the machine uses — including unrelated applications, IDE helpers, and other JVMs. On a busy
    developer machine that causes two kinds of test flake: a foreign process can occupy a number a test is about to
    choose, and a test can momentarily connect to that foreign listener instead of the server under test. Setting
    both mockserver.testPortRangeStart and mockserver.testPortRangeEnd (for example
    -Dmockserver.testPortRangeStart=20000 -Dmockserver.testPortRangeEnd=40000) makes PortFactory choose ports by
    explicitly binding inside that band — which the OS does not itself hand out to bind(0) — retrying past any
    number already in use. Both properties are unset by default, so continuous integration and any machine that does
    not set them keep the exact bind(0) behaviour as before. This affects only test port selection; it does not
    change how MockServer binds its own ports at runtime (starting on port 0 and reading back the assigned port
    remains the fully race-free option and is unchanged). In a surefire/failsafe fork the two properties must reach
    the fork, e.g. via -Dmockserver.testArgLine="-Dmockserver.testPortRangeStart=20000 -Dmockserver.testPortRangeEnd=40000".
Changed
  • HTTP/2 now gives every request stream its own channel. MockServer's HTTP/2 server — both h2 (over TLS) and
    cleartext h2c — now uses Netty's stream-multiplexing model for every connection, replacing the previous
    shared-connection HTTP/2 pipeline. Each HTTP/2 stream is processed on its own isolated child channel. This
    removes a class of cross-stream interference on busy connections: concurrent streaming responses (Server-Sent
    Events, NDJSON, AWS Bedrock event-stream, and therefore all streaming LLM responses) can no longer have a later
    chunk of one stream mis-routed onto another, and each stream's end-of-stream is delivered independently, so a
    slow or streaming response on one stream cannot stall another. This model was previously used only when
    grpcBidiStreamingEnabled was set; it is now the standard HTTP/2 pipeline and needs no configuration — so the
    HTTP/2 correctness fixes listed below apply to all HTTP/2 traffic, not only when that flag is enabled. There is
    no change to the REST/Java API, to how expectations are written, or to HTTP/1.1 and HTTP/3 traffic, and HTTP/2
    clients receive the same responses. Users driving very large numbers of concurrent streams over a single
    connection may notice different memory and throughput characteristics, since each stream now has its own
    lightweight channel. (GitHub issue #​2669).
  • Behaviour change — over HTTP/2, closeSocket now ends the stream, not the connection. A per-expectation
    ConnectionOptions.closeSocket / closeChannel (and the slowCloseDelay connection-lifecycle chaos) applied
    to an HTTP/2 request now closes only that request's stream; other requests in flight on the same connection
    continue and complete normally. Previously it tore down the whole TCP connection, killing every concurrent
    stream on it — so one expectation could destroy unrelated clients' in-flight requests. Ending a single stream
    is the correct HTTP/2 semantic and is what the same expectation already did over HTTP/1.1, where a connection
    carries one request at a time. If you rely on closeSocket to tear down an HTTP/2 connection — for example
    to test how your client recovers from a dropped connection — use the resetMidResponse connection-lifecycle
    chaos fault instead, which still aborts the whole TCP connection (that is its purpose, and it now does so
    properly rather than degrading into a single-stream reset). HTTP/1.1 and HTTP/3 behaviour is unchanged.
    (GitHub issue #​2669).
  • BREAKING: mockserver-bom now manages only MockServer's own org.mock-server modules — it no longer pins
    the third-party libraries MockServer uses internally.
    The published BOM previously baked in MockServer's
    entire parent dependencyManagement (~190 third-party entries — Jackson, Netty, Guava, Nimbus, Velocity, and
    more), four of them at test scope. Because a BOM's managed versions and scopes apply to whoever imports it,
    this silently overrode a consumer's own versions and scopes for those shared libraries. For example, a
    project that declared com.nimbusds:oauth2-oidc-sdk without a scope had it forced onto the test classpath
    by MockServer's internal test-scoped pin, so the dependency disappeared from compile/runtime and the
    consumer's production code failed to build against it (GitHub issue #​2684). The published BOM now contains
    only the MockServer module entries (24, down from 221), so importing it never changes any third-party version
    or scope in your build. What you need to do: if you imported mockserver-bom to align MockServer's
    transitive third-party versions — for instance to satisfy the Maven Enforcer dependencyConvergence rule —
    those pins are gone and convergence errors may reappear; manage the affected third-party versions yourself in
    your own dependencyManagement, or import each upstream project's own BOM. Aligning the MockServer modules
    themselves is unchanged: keep importing the BOM and declare MockServer artifacts without a version.
    (GitHub issue #​2684).
Fixed
  • A SOCKS4a client (for example curl -x socks4a://…) proxying through MockServer no longer hangs.
    SOCKS4a is the SOCKS4 extension where the client sends the destination as a hostname instead of an
    IPv4 address. MockServer decoded the request correctly but then echoed that hostname back into the
    DSTIP field of the SOCKS4 grant reply, which must be an IPv4 literal; the resulting error was thrown
    while writing the reply, so the client never received one and blocked until it timed out (curl exit 28,
    0 bytes) — for cleartext HTTP as well as for TLS. The reply's DSTIP/DSTPORT are ignored by clients,
    so a SOCKS4a grant now carries 0.0.0.0:0; a classic SOCKS4 request (IPv4 literal) still echoes its
    destination back unchanged. Plain SOCKS4 and SOCKS5 (socks5h://) were unaffected.

  • The command-line server now exits with a non-zero status code when it fails to start. Previously, if
    MockServer could not start — most commonly because the requested port was already in use — the CLI logged
    the error but still exited 0, so a shell script or CI job that started MockServer got no failure signal
    and carried on as though the server was up (typically failing later with a confusing connection error).
    mockserver run / -serverPort (and the ui, demo, proxy, and openapi subcommands, which start a
    server the same way) now exit 1 on a failed start. Usage errors that were already handled — an invalid or
    missing port, an invalid host or log level — keep their existing exit code, so only the previously-silent
    startup-failure case changes.

  • An HTTPS request that negotiates HTTP/2 (ALPN h2) through MockServer's SOCKS proxy now works.
    Previously, using MockServer as a SOCKS4/SOCKS5 proxy for an https:// request that upgraded to HTTP/2
    failed completely — the client received nothing (curl reported CURLE_HTTP2, 0 bytes) — because MockServer
    provisioned its internal relay for HTTP/1.1 before the tunnelled TLS connection had negotiated its protocol,
    so the forwarded HTTP/2 frames were unparseable. SOCKS over HTTP/1.1, SOCKS over cleartext, and the HTTP
    CONNECT proxy over HTTP/2 were unaffected and continue to work. MockServer now terminates the tunnelled TLS
    in the relay and waits for its ALPN result before wiring up the connection, exactly as the CONNECT proxy
    already did. This works on any port: rather than guessing from the destination port number (the earlier
    fix inferred TLS only for ports ending in 443, so h2 to a TLS port such as 993, 465, or 9999 was
    still mis-provisioned as HTTP/1.1), MockServer now classifies the first bytes the client sends through the
    tunnel — a TLS record versus a cleartext HTTP request — and provisions the connection to match. As a result,
    HTTP/2 over TLS through a SOCKS tunnel succeeds on any port, and a cleartext tunnel to a port that happens to
    end in 443 is no longer mistaken for TLS. Cleartext HTTP/2 with prior knowledge (h2c) through a SOCKS
    tunnel now works too
    — the last case left as HTTP/1.1 by the earlier fixes. The same first-bytes
    classification now also recognises the HTTP/2 connection preface (PRI * HTTP/2.0…) after ruling out a TLS
    record, and provisions cleartext HTTP/2 on both relay legs so the mocked response is served over h2c
    (a curl --http2-prior-knowledge request through a socks5h:// proxy, for example); anything that is not a
    preface is still provisioned as HTTP/1.1, and when HTTP/2 is disabled (http2Enabled=false) the preface is
    ignored and the tunnel falls back to HTTP/1.1, exactly as MockServer's own listener does. The HTTP CONNECT
    proxy is unchanged, so cleartext HTTP/2 with prior knowledge through CONNECT is still served as HTTP/1.1 —
    that was never a working case and is unaffected either way. (GitHub issue #​2685).

  • Starting the command-line server with port 0 now reports and uses the actual OS-assigned
    ephemeral port instead of 0. Previously mockserver run -p 0 (or -serverPort 0) bound a real
    ephemeral port but recorded the requested 0, so the port a caller needs to reach the server was
    not discoverable, and ui -p 0 / demo -p 0 printed a dashboard/getting-started URL pointing at
    localhost:0. MockServer now records the real bound port after startup and uses it for the
    dashboard and demo URLs. Starting on an explicit, non-zero port is unaffected.

  • HTTP/2 responses larger than the client's initial flow-control window no longer hang when fetched
    through MockServer's HTTPS forward proxy (HTTP CONNECT). On the CONNECT-tunnel path several handlers
    ahead of the HTTP/2 codec overrode Netty's channelReadComplete to only flush, without propagating the
    event down the pipeline. Netty's HTTP/2 connection handler relies on channelReadComplete to flush
    flow-control-pending writes (it is where a peer's WINDOW_UPDATE is acted on), so swallowing the event
    stalled any h2 response bigger than the peer's initial window (65,535 bytes by default) at exactly one
    window until the client timed out. Direct HTTP/2 (h2c and TLS+ALPN) and HTTP/1.1-through-CONNECT
    were unaffected. The affected handlers now propagate the event. (GitHub issue #​2683).

  • The S3 blob-store tests pull MinIO from quay.io instead of Docker Hub. minio/minio is no longer
    pullable from Docker Hub, which broke these tests — and therefore the build — with a container-fetch
    error unrelated to any code change. quay.io is MinIO's other official registry and serves the same
    image and tag. Test-only; nothing MockServer ships is affected.

  • The Python client and the Python Testcontainers module now ship a PEP 561 py.typed marker, so type
    checkers use their annotations instead of ignoring them. Both packages are almost fully annotated, but
    without the marker mypy reported Skipping analyzing "mockserver": module is installed, but missing library stubs or py.typed marker and treated every import as Any, so no call into the client was
    checked at all. Nothing about the packages' behaviour changes — mypy (and any PEP 561 checker) will now
    type-check your calls, which may surface genuine mistakes in existing code that were previously
    invisible. (GitHub issue #​2680).

  • With gRPC bidi-streaming enabled (grpcBidiStreamingEnabled), streaming responses over HTTP/2 — Server-Sent
    Events, NDJSON, AWS Bedrock event-stream, and therefore all streaming LLM responses — now terminate correctly
    instead of leaving the client hanging. Enabling that mode routes every HTTP/2 stream (not just gRPC) through
    Netty's multiplex model, where each stream is a separate child channel. On that path MockServer's terminal
    end-of-stream marker was being silently dropped by Netty's stream-frame codec, which hard-codes streaming data
    frames as "not the end of the stream". The client received every event and then waited — receiving no
    end-of-stream — until it timed out, with nothing failing or logged server-side. MockServer now translates the
    terminal frame so the codec emits it with the end-of-stream flag set, closing the stream as expected. Streaming
    over HTTP/1.1 and over the default (non-multiplex) HTTP/2 pipeline was already correct and is unaffected. (GitHub issue #​2669).

  • With gRPC bidi-streaming enabled (grpcBidiStreamingEnabled), plain HTTP requests sharing the same HTTP/2
    connection are no longer mis-handled. Enabling that mode switches the HTTP/2 pipeline to Netty's multiplex
    model, giving every stream its own child channel — but those child channels do not inherit the parent
    connection's attributes, and MockServer had only been copying one of them across. As a result, on any
    non-gRPC request arriving over such a connection: it was not recognised as HTTP/2 (so withProtocol(HTTP_2)
    matching failed and the protocol was mis-reported in the request log and HAR export); a request to the
    dashboard or callback WebSocket endpoint tried to perform a WebSocket handshake over the HTTP/2 stream — which
    is unsupported — instead of cleanly returning 501 Not Implemented; a client certificate presented on the
    connection was not visible to control-plane authentication for MCP requests on that stream; and a proxied
    request (SOCKS/CONNECT/transparent/port-forward) was mis-routed — treated as a direct request, or forwarded to
    its Host header instead of the proxy's actual remote target. The connection-scoped state a request depends on
    (negotiated protocol, TLS/client-certificate details, the proxying flag, the proxy remote-target address, and
    the local-host set) is now propagated onto each HTTP/2 stream child channel, so these requests behave exactly as
    they do on HTTP/1.1 and on the default (non-multiplex) HTTP/2 pipeline. HTTP/1.1 traffic is unaffected. (GitHub issue #​2669).

  • With gRPC bidi-streaming enabled (grpcBidiStreamingEnabled), a plain HTTP request sent over HTTP/2 with a
    compressed body (content-encoding: gzip, deflate, and so on) is now decompressed before matching. Enabling
    that mode routes every HTTP/2 stream — ordinary requests included, not just gRPC — through Netty's multiplex
    model, and on that path the request body was left compressed: a withBody(...) expectation then silently
    failed to match (MockServer answered 404 Not Found) and the recorded request showed unreadable compressed
    bytes. Compressed request bodies are now decompressed on this path exactly as they are on HTTP/1.1 and on the
    default (non-multiplex) HTTP/2 pipeline. gRPC's own message compression (carried by grpc-encoding) is a
    separate mechanism and is unaffected. (GitHub issue #​2669).
    Matching on the content-encoding header itself still works: the header is preserved for matching before
    decompression removes it, exactly as on HTTP/1.1. (An expectation written against content-encoding: gzip
    briefly stopped matching on this pipeline once decompression was added; that is fixed here.)

  • With gRPC bidi-streaming enabled (grpcBidiStreamingEnabled), a plain HTTP request sent over HTTP/2 with an
    unusual header value — a leading space, an embedded DEL (0x7F), or another control character — is now
    received and matchable instead of being silently rejected. Enabling that mode routes every HTTP/2 stream
    through Netty's multiplex model, where such a request was reset with RST_STREAM(PROTOCOL_ERROR) before it
    ever reached the matchers, so nothing matched and nothing was logged as received. Because MockServer is a mock
    server that users deliberately drive with malformed traffic to test their own clients, these requests are now
    accepted and recorded, matching the behaviour on HTTP/1.1 and on the default (non-multiplex) HTTP/2 pipeline.
    This leniency applies to inbound requests only: response header names MockServer sends are still validated
    as before, exactly as on the default HTTP/2 pipeline, so a malformed response header name is rejected rather
    than put on the wire. (GitHub issue #​2669).

  • Over HTTP/2 the two ways of ending a connection now behave distinctly instead of collapsing into a single
    stream reset (see the behaviour-change note above). Every HTTP/2 stream is its own child of the shared TCP
    connection.
    A per-expectation closeSocket / closeChannel (and the slowCloseDelay connection-lifecycle chaos) now
    correctly ends only that request's stream, leaving other concurrent requests on the same connection to
    complete normally — whereas the resetMidResponse connection-lifecycle chaos fault, whose purpose is to
    simulate a real server socket abort, now resets the whole TCP connection (aborting every concurrent stream on
    it) rather than quietly degrading into a single-stream reset. Previously resetMidResponse over multiplex
    HTTP/2 emitted only an ordinary stream reset — a fault meant to simulate a crashed socket silently became
    something weaker. Behaviour over HTTP/1.1 and over the default (non-multiplex) HTTP/2 pipeline is unchanged.
    (GitHub issue #​2669).

  • The MockServer dashboard is no longer unreachable over HTTP/2 — a browser (or any client) requesting
    /mockserver/dashboard and its assets over HTTP/2 hung indefinitely and never received a response. The
    dashboard handler writes its response straight to the channel (it does not go through the normal response
    writer), and it never copied the request's HTTP/2 stream id onto that response. On the shared HTTP/2
    connection Netty then routed the response head onto a fresh server-initiated stream instead of the client's
    own stream, so the response was never delivered and the client waited until it timed out. Nothing failed or
    was logged server-side, which is why it went unnoticed. The handler now stamps the request's stream id onto
    every response it writes (matching the metrics endpoint, which shares the same direct-write pattern); this is
    a no-op on HTTP/1.1, where the dashboard already worked.

  • MockServerContainer (the Testcontainers integration) now waits until MockServer is actually serving
    before start() returns, so a request issued immediately afterwards is no longer reset. The container waited
    with a listening-port strategy, which is satisfied the instant the mapped port accepts a TCP connection — but
    MockServer's Netty listener binds the port early and then accepts-then-resets connections until initialisation
    finishes. start() therefore returned inside that window (measured at ~0.2–0.3s wide against the released
    image, and wider on a loaded host), and the first request could fail with SocketConnectionException: Channel handler removed before valid response has been received. The wait is now an HTTP readiness probe against
    PUT /mockserver/status returning 200, which only happens once the request pipeline is fully initialised, so
    start() returning now means "ready to serve". This most affects callers on busy/CI hosts, where CPU
    contention widens the race. (withServerPort(...) re-targets the probe at the chosen port.)

  • A streaming httpLlmResponse expectation that sets completion.streamingPhysics.timeToFirstToken is no
    longer rejected with an HTTP 400 and a confusing error naming org.mockserver.model.Delay — a type the user
    never wrote. The expectation serialised cleanly on the client (a raw Delay serialises to exactly the same
    bytes its DelayDTO produces), so the invalid JSON was only rejected server-side on deserialisation, where
    Jackson tried to construct a raw Delay (which has no default constructor and no creator) instead of going
    through DelayDTO. This serialise-succeeds / deserialise-fails asymmetry is why it was invisible in
    client-side tests. timeToFirstToken now crosses the wire through the DTO layer like every other Delay
    (via new CompletionDTO/StreamingPhysicsDTO boundaries wrapping it in DelayDTO); the serialised JSON is
    byte-for-byte unchanged, so existing stored expectations and clients keep working. (GitHub issue #​2668).

  • Two (or more) concurrent streaming responses (httpSseResponse, and streaming httpLlmResponse with
    completion.streaming:true) over a single HTTP/2 connection no longer cause one stream to hang forever. All
    non-gRPC HTTP/2 traffic is multiplexed over one shared HttpToHttp2ConnectionHandler, which picked the
    outbound target stream from a single mutable field updated only when a response head was written; a bare
    data chunk carries no stream id, so once a second stream wrote its head every later chunk of the first
    stream was mis-routed onto the second (by then often already-closed) stream — the server logged
    IllegalArgumentException: Stream no longer exists, the terminal frame never reached the first stream, and
    its client waited on a stream that would never end. Streaming data frames now carry their originating stream
    id out-of-band (a StreamAddressedHttpContent wrapper) so each frame is written onto its own stream, and
    interleaved concurrent streams each receive their full body and their own END_STREAM; a streaming write
    that fails for any reason now still ends its own stream so a client is never left hanging. httpLlmResponse streaming, which is served through the same handler, was
    equally affected and is fixed by the same change. (GitHub issue #​2667).

  • A JSON body expectation built through a client (e.g. json("{\"amount\":275.0}", MatchType.ONLY_MATCHING_FIELDS))
    no longer fails to match a byte-identical request. A whole-number double such as 275.0 was silently corrupted
    to the bare integer 275 when the expectation was serialised, before any request even arrived: the JSON body
    serializers parsed the value with USE_BIG_DECIMAL_FOR_FLOATS into a BigDecimal, and Jackson's
    STRIP_TRAILING_BIGDECIMAL_ZEROES (on by default since 2.15) stripped the trailing zero at node construction,
    so re-serialisation emitted an integer literal. The server then parsed 275 as an integer while the request's
    275.0 stayed a double, and json-unit correctly reports those as different — so the expectation never matched
    the very request it was created for. The serializers now keep BigDecimals exact
    (JsonNodeFactory.withExactBigDecimals(true)), so 275.0 survives as 275.0 and genuine integers such as 1
    are left untouched. This also completes the original intent of the earlier USE_BIG_DECIMAL_FOR_FLOATS fix
    (#​1740), which was meant to preserve decimals such as 0.00 but — because the same stripping was already active
    at that time — had never actually done so. (GitHub issue #​2658).

  • Streaming responses (httpSseResponse, and streaming httpLlmResponse with completion.streaming:true)
    no longer wrongly close the connection at end of stream. finishStream always closed by default —
    closeConnection defaults to null and the old null || true test made "unset" mean "always close" — while
    the response head unconditionally advertised Connection: keep-alive. So an HTTP/1.1 client that reused the
    connection the response had promised it could keep got a RemoteDisconnected on its next request, and on
    HTTP/2 (where every non-gRPC stream is multiplexed onto one connection channel) the ctx.close() emitted
    GOAWAY and tore down the whole connection, killing sibling streams. The end-of-stream decision now mirrors
    the non-streaming path: an explicit closeConnection still wins on HTTP/1.1, otherwise the request's
    keep-alive intent decides and alwaysCloseSocketConnections still forces a close; an HTTP/2 request never
    closes the shared parent connection (its terminal frame ends only that stream); and the Connection header
    now reports the decision that is actually taken instead of always claiming keep-alive. (GitHub issue #​2641).

    Note: fully-interleaved concurrent HTTP/2 streaming over the single-connection HTTP/2 path was a separate
    limitation (bare content frames were routed by a single current stream id) and is now fixed too — see #​2667.

  • Request bodies sent as application/yaml, application/x-yaml or application/graphql are no longer
    corrupted. None of those subtypes were in MediaType.isString(), so the body was stored as a BinaryBody
    and getBodyAsString() handed back base64 — silently mangling every YAML specification and GraphQL SDL
    document sent with its natural content type, on the control plane (PUT /mockserver/graphql rejected its
    own documented example this way, and PUT /mockserver/openapi did the same for a YAML spec) and in user
    request matchers alike. application/yaml is the media type registered by RFC 9512; all three are UTF-8
    text with neither a text type nor a +json/+xml suffix to fall back on.

  • PUT /mockserver/contractTest no longer reports success for a run that verified nothing. allPassed was
    computed as passed == results.size(), which is vacuously true for an empty result set, so a contract test
    whose operationId filter was mistyped or had gone stale against a renamed operation returned
    {"totalOperations": 0, "allPassed": true}. An empty run is now allPassed: false and carries an error
    naming why nothing ran.

  • The published OpenAPI examples for PUT /mockserver/asyncapi/http, PUT /mockserver/loadScenario/generateFromOpenAPI
    and PUT /mockserver/contractTest now work as published. The AsyncAPI endpoint declared no request-body
    example at all (so the generated Postman/Bruno collections sent an empty body and the endpoint rejected it),
    and the other two fetched their specification from a remote URL, so the example failed anywhere without
    egress. Both now carry an inline specification.

  • With gRPC bidi-streaming enabled (grpcBidiStreamingEnabled), the HTTP/2 GOAWAY drain signal now actually
    reaches the client. Enabling that mode switches the HTTP/2 pipeline to Netty's multiplex model, where each
    stream is handled on its own child channel. The code that emits a connection-level GOAWAY looked for the
    HTTP/2 connection handler only on the local channel — but on a multiplex child channel that handler lives on
    the parent connection channel, so the lookup found nothing and the GOAWAY was silently dropped. Two
    "tell the client to drain" signals stopped working as a result: the graceful drain GOAWAY sent while the
    server is preempting/shutting down (so HTTP/2 clients stop opening new streams and retry elsewhere), and the
    http2GoAway chaos experiment. In both cases the server believed it had signalled while the client never
    learned, with nothing logged. The emitter now walks up to the parent connection channel when needed, so the
    GOAWAY is written on the connection where it belongs. The default (non-multiplex) HTTP/2 path and HTTP/1.1
    are unaffected. (GitHub issue #​2669).

Changed
  • PUT /mockserver/loadScenario/generateFromRecording answers 409 rather than 400 when there is no
    recorded traffic to convert; 400 now means only that the request is malformed (missing name, an invalid
    mode, or a scenario that fails validation). The body was always well-formed in the no-traffic case — only
    the state was missing — so 400 told callers their request was wrong and gave them no way to tell that apart
    from "record some traffic through the proxy first". The core throws a dedicated
    LoadScenarioFromRecording.NoRecordedTrafficException, which extends IllegalArgumentException so existing
    callers that catch it keep working. This was the last entry in the collection gate's KNOWN_FAILING
    ratchet, which is now empty: every example the published Postman/Bruno collections carry is accepted by a
    default container.
  • PUT /mockserver/verifySLO answers 403 rather than 400 when SLO tracking is disabled
    (sloTrackingEnabled=false); 400 now means only that the criteria are malformed. The two cases previously
    shared 400, so a caller could not tell a typo in its criteria from a server with the feature switched off —
    every client library papered over it with a combined "invalid criteria (or SLO tracking disabled)" message.
    403 matches PUT /mockserver/loadScenario/start, which is the same situation, and the feature-disabled
    error the clients already model. All nine clients now distinguish the two: Go returns FeatureDisabledError
    on 403 and a criteria error on 400, PHP throws FeatureNotEnabledException on 403, Java throws
    IllegalStateException on 403 and keeps IllegalArgumentException for 400, and the Python, Ruby, Node,
    Rust, .NET and dashboard clients give distinct messages for each.
Added
  • The initialization-file watch poll interval is now configurable via the new
    watchInitializationJsonPollPeriodMillis property (system property -Dmockserver.watchInitializationJsonPollPeriodMillis,
    env var MOCKSERVER_WATCH_INITIALIZATION_JSON_POLL_PERIOD_MILLIS, properties-file key, Configuration
    instance setter, and PUT /mockserver/configuration). It controls how often a watched
    initializationJsonPath / initializationOpenAPIPath file (when watchInitializationJson=true) is polled
    for changes; default 5000 (5 seconds) — unchanged from the previously hard-coded value — lower it for
    faster live reloads or raise it to reduce polling overhead. Previously this interval was a process-wide
    mutable static with no supported configuration route, only reachable through internal test-only setters.
    Those four accessors (FileWatcher.get/setPollPeriod and get/setPollPeriodUnits) are removed — they were
    public static on an internal persistence class, were never a documented configuration route, and had no
    consumers outside MockServer's own tests; the property above replaces them. The shared mutable static was
    also a real defect: two test classes shortened it and restored it concurrently, so whichever finished first
    reinstated the 5-second default under the other, which is what reddened master builds 6914/6918 and PR #​2655.
  • ORCAROUTER is now a supported LLM provider. OrcaRouter (api.orcarouter.ai) is an OpenAI-chat-compatible
    AI gateway that fronts many upstream models with vendor-prefixed model ids, so it is wired exactly like
    OPENROUTER: it produces the OpenAI Chat Completions wire format, is detected from its host on proxied
    traffic, and prices vendor-prefixed model ids through the underlying vendor's table. This completes community
    contribution #​2545 by adding ORCAROUTER to
    the two consumer-facing published contracts the server-side change had left out — the published OpenAPI
    specification's httpLlmResponse.provider enum and the Node client's LlmProvider type union — so the
    provider is reachable from generated tooling and the TypeScript client, and by documenting it on the LLM
    response mocking page.
  • PUT /mockserver/retrieve now accepts an expectation id ({"id": "..."}) in the request body instead of a request
    matcher, matching what PUT /mockserver/clear and PUT /mockserver/verify have always accepted (GitHub issue #​2591).
    For ?type=active_expectations this returns only the expectation with that id — filtering on the id itself, not on
    the request definition it resolves to, so it neither returns every other expectation whose matcher matches the same
    request nor misses expectations (OpenAPI, schema or regex matchers) whose own definition does not match their own
    matcher. For requests, request_responses, recorded_expectations and logs it returns the entries matching that
    expectation's request, exactly as verify by expectation id matches them. An unknown id is rejected with
    400 No expectation found with id ... rather than silently matching everything. The expectation-id body and the
    request-matcher body are unambiguous — both JSON schemas set additionalProperties: false and only the expectation
    id schema allows (and requires) an id property — so existing request-matcher and empty bodies are unaffected. All
    nine clients gained the corresponding calls: Java retrieve*ById(...), Node retrieve*ById(...), Python/Ruby
    retrieve_*_by_id(...), Go Retrieve*ByID(...), Rust retrieve_*_by_id(...), PHP retrieve*ById(...) and
    .NET Retrieve*ById(...).
  • The LLM provider list in the two consumer-facing published contracts — the OpenAPI specification
    (jekyll-www.mock-server.com/mockserver-openapi.yaml, schema HttpLlmResponse.provider) and the Node client's
    LlmProvider type union (mockserver-client-node/mockServer.d.ts) — is now pinned to the server's Provider enum
    by ProviderConsumerContractEnumParityTest. Previously a new provider could be added to Provider and every
    server-side registration point while both published contracts were left behind, and nothing failed: the existing
    internal-schema parity test does not read these files, and the Node drift test compares the .d.ts only against the
    OpenAPI spec, so the two consumer copies could drift from the server in lock-step yet still agree with each other.
    Both consumer artefacts are now driven from the one authority (the compiled Provider enum), in both directions, and
    each failure message names the missing provider, the exact file, and how to fix it.
  • Pull requests from third-party forks now get an automated test signal, via a new GitHub Actions workflow
    (.github/workflows/pr-tests.yml). Buildkite — the primary CI — deliberately does not build fork PRs
    (build_pull_request_forks: false) because its EC2 agents carry the signing key and every publish credential, so
    community PRs previously got no test feedback at all. The new workflow runs the full Maven reactor clean install
    (Surefire unit tests and Failsafe integration tests, including the Docker-gated Testcontainers suites, which execute
    against local emulator containers on the Docker-enabled hosted runner — no cloud credentials) plus the standalone
    examples build, and uploads the failing tests' reports as an artifact so the contributor can see which test failed
    and why. It is secret-free by construction — on: pull_request (never pull_request_target), no secrets.*
    reference, workflow-level permissions: {} with the one job granted only contents: read, and GitHub-owned
    SHA-pinned actions only — so it is useless to an attacker who controls the code it runs. It runs only on fork PRs
    (where Buildkite adds nothing) and on manual workflow_dispatch; in-repo branches keep their full Buildkite
    pipeline. The three privileged transparent-proxy end-to-end suites are excluded explicitly here — by name, via
    -Dfailsafe.excludesFile, which preserves the POM includes and drops only those classes — because they build
    --cap-add=NET_ADMIN/--privileged sibling containers a hosted runner cannot be relied on to support, and their
    only code-level gate (DockerCliTestSupport.isDockerAvailable()) is true on ubuntu-latest (Maven has no
    RUN_TRANSPARENT_PROXY_E2E gate — that is a Buildkite shell-step switch only); they, and the client-language/UI/Helm
    pipelines, remain Buildkite's responsibility. The Docker-gated Testcontainers suites that do run are paired with a
    fail-closed assert-suite-ran.sh check, so a runner without a usable Docker daemon reds the job rather than passing
    green on skipped coverage.
  • Dependabot minor/patch pull requests are now merged automatically once every check that actually exists on the
    PR head commit has genuinely passed, via a new GitHub Actions workflow (.github/workflows/dependabot-auto-merge.yml).
    It was chosen deliberately over GitHub's native auto-merge: native auto-merge waits only on the branch-protection
    required-status-check list, and in this repo Buildkite and both Snyk scans are not required checks, so native
    auto-merge would merge the instant the required checks were green while the build or the vulnerability scanner was
    still pending or red — and a newly-added check stays invisible to it until someone edits the list. Instead the workflow
    reads the checks that actually exist by aggregating both GitHub APIs — the Check Runs API
    (/commits/{sha}/check-runs, which on a Dependabot head carries CodeQL and the language Analyze runs) and the
    legacy Commit Status API (/commits/{sha}/status, which carries buildkite/mockserver and both Snyk scans) — and
    requires them to pass. Commit statuses must be success — the legacy status API has only error/failure/pending/
    success, so the security-critical contexts cannot report a non-blocking state. Check runs may be skipped or
    neutral without blocking (the fork-only test workflow is always skipped on an in-repo PR, so requiring literal
    success from every run would merge nothing), but at least one genuine success is still required. It fails closed on
    every other ambiguity: any pending, failed, cancelled or unrecognised result, zero checks found (the empty-set
    trap), an API error, or unknown mergeability all refuse the merge with a logged reason. It merges only PRs authored by dependabot[bot] whose head branch is in this
    repository (not a fork), and only two branch classes: minor/patch group updates and Docker digest bumps. For
    the first, .github/dependabot.yml groups minor+patch into *-minor-and-patch groups and excludes majors, so a major
    arrives as an ungrouped single-dependency branch that fails the group-branch check — a structural, spoof-resistant
    signal because only Dependabot creates those in-repo branches. For the second, a Docker image-digest bump (a new SHA
    on an unchanged tag) carries no semver and so joins no group; it is now accepted directly, but only for the
    distroless runtime bases — the one image class that, being pinned on non-semver tags, has only ever moved by digest in
    this repo's entire Dependabot history — and only when two independent Dependabot-generated signals agree: the
    branch is a .../distroless/<image>-<hex-sha> path and the PR title is Dependabot's digest-bump shape (bump … from <hex-sha> to <hex-sha>). A version or tag bump matches neither (its branch and title carry bare versions, not hex
    shas), and the two-signal AND fails closed, so a major or tag change can never be auto-merged. The semver-tagged
    images (alpine, ubuntu, eclipse-temurin, grafana/k6) are deliberately excluded from the digest path — safety
    over completeness. .github/dependabot.yml is unchanged.
    The workflow runs on a schedule sweep plus workflow_dispatch (never pull_request/pull_request_target), checks
    out no PR code, uses no third-party actions and no secrets.*, and holds only job-level contents: write +
    pull-requests: write under a workflow-level permissions: {}. It ships with dry_run defaulting ON so it can be
    trialled — logging each decision without merging — until the schedule default is flipped to live.
Changed
  • The Dependabot auto-merge workflow (.github/workflows/dependabot-auto-merge.yml) is now live on the schedule:
    DEFAULT_DRY_RUN was flipped from 'true' to 'false', so the scheduled sweep actually merges eligible PRs rather
    than only logging its decisions. The workflow_dispatch dry_run input still defaults to true, so a manual run
    stays a safe dry-run probe unless the operator explicitly sets it false. Arming it is backed by a new standing guard
    (Rule 6 in .buildkite/scripts/steps/check-false-green-guards.sh) that fails the build if any of the four
    load-bearing properties keeping majors and version bumps out of auto-merge is quietly weakened: it parses the three
    acceptance regexes out of the workflow and asserts the digest-branch regex stays anchored, confined to
    dependabot/docker/, keeps its literal /distroless/ scope and a trailing hex-run floor of at least 7 (without the
    scope a date-tagged bump such as bump ubuntu from 202401151200 to 202402201200, whose date tokens are all hex, would match
    the title regex); the digest-title regex requires a [0-9a-f]{7,} run on both the from and to sides; path B still
    cross-checks the title as a conjunct of the branch shape and fails closed on mismatch; and every group in
    .github/dependabot.yml whose branch name path A accepts declares update-types within {minor, patch}, so a major
    can never ride a branch literally named *-minor-and-patch. The guard fails closed if the workflow is missing, a
    regex cannot be extracted, path B cannot be located, or no group is accepted by path A.
  • Dependabot no longer proposes TypeScript 7.x for the Node/UI packages. typescript-eslint's peer range is
    typescript >=4.8.4 <6.1.0, so a TypeScript 7 bump fails npm ci with ERESOLVE before lint or typecheck can run,
    and nothing else in the tree raises that ceiling — the upgrade is unmergeable until typescript-eslint ships
    support for the new major. The ignore carries the reason and an explicit revisit condition, so it is a recorded
    decision rather than silent suppression.
  • Dependabot version-ceiling ignores that guard a dependency declared in the reactor parent mockserver/pom.xml
    are now mirrored into every Dependabot block whose module inherits that parent — /examples/java and
    /mockserver/mockserver-maven-plugin. Because those child directories resolve up into the parent pom, Dependabot
    scanning them could bump a parent-declared dependency and brand the PR with the child scope, sidestepping an ignore
    that existed only in the /mockserver block. That is exactly how the un-passable checkstyle 12.3.1 -> 13.10.0
    PR #​2554 was generated from /examples/java (checkstyle 13 ships Java 21 bytecode; the project floor is Java 17).
    The checkstyle >= 13.0.0 and graphql-java ceilings are now present in all three parent-reaching blocks;
    the module-only infinispan-core ignore is deliberately not mirrored because it is unreachable from those scopes.
    Each block carries a SYNC-WITH-PARENT note so future ignores are added in every parent-reaching block.
  • Corrected a second, freshly-introduced ceiling suppression of the same class. The typescript >= 7.0.0 ignore
    added on 2026-08-17 was placed on the npm block shared by /mockserver-ui, /mockserver-client-node and
    /mockserver-node, but its rationale (a typescript-eslint peer conflict) applies only to the UI.
    mockserver-client-node was already on typescript ~7.0.2 — above the ceiling — and carries no
    typescript-eslint, so the ignore silently froze every typescript update for that published package.
    /mockserver-ui now has its own block carrying the ignore, mirroring how /mockserver-vscode was already
    split for the same reason: a directory-specific constraint must not leak onto directories it does not apply to.
  • Corrected a Dependabot ceiling that had been suppressing every graphql-java update, including security patches.
    The ignore read >= 25.0.0 while mockserver/pom.xml had already moved to 26.0, so the in-use version was itself
    above the ceiling and no candidate could ever be proposed. This was found while mirroring the ignore into the two
    parent-reaching blocks — the mirror would have propagated the fault rather than the control. Raised to >= 27.0.0
    in all three blocks and documented the invariant inline: a ceiling must stay above the version in use, because
    one at or below it is indistinguishable from a working ceiling — no pull requests appear either way. The stale
    "22.x line" comments in mockserver/pom.xml and the < 25.0.0 row in docs/operations/security.md are corrected
    to match the 26.x reality.
  • A managed org.jspecify:jspecify version (1.0.1) is now pinned in mockserver/pom.xml <dependencyManagement>
    so maven-enforcer's DependencyConvergence stays green across Guava and graphql-java bumps. Guava 33.7.0-jre
    pulls jspecify 1.0.1 while graphql-java -> java-dataloader:6.0.0 pulls 1.0.0; with no managed version the two
    transitive paths diverge the moment Guava moves, which is why Dependabot PR #​2555 (guava 33.6.0 -> 33.7.0) failed
    the enforcer. jspecify is an annotation-only artifact and 1.0.1 is a backward-compatible patch that neither
    consumer rejects, so the higher line is pinned.
Removed
  • BREAKING BEHAVIOUR: org.mock-server:mockserver-examples is no longer published to Maven Central, and the
    org.mockserver.examples JPMS automatic module is no longer resolvable.
    This artifact was only ever sample code
    demonstrating client and proxy usage — not a supported consumer dependency — but it had been GPG-signed and deployed
    to Maven Central on every release (it was a <module> of the mockserver reactor, and the release deploy runs
    mvn deploy -P release over that whole reactor with no -pl restriction). It has now been removed from the mockserver
    reactor, so the release deploy no longer reaches it and no new versions will appear on Central
    (skipPublishing=true in its POM guards against a future re-add). Previously published versions (7.6.0 and earlier)
    remain available and resolvable forever
    — only new releases are affected. Anyone who declared a dependency on
    mockserver-examples or requires org.mockserver.examples should pin the last published version or, preferably,
    copy the sample code (examples/java/) into their own project. This is marked BREAKING BEHAVIOUR rather than plain
    BREAKING because it does not force a major version bump: the artifact was never a supported dependency, no shipped
    MockServer artifact depends on it, and every previously published version stays available.
Fixed
  • Fixed an order-dependent failure in the blob-store registrar tests, surfaced by running the suite on hardware
    with a different class ordering. S3ExpectationPersistenceReloadTest starts a real server with
    blobStoreType("s3"), which makes StateBackendFactory discover and register the s3 factory in its JVM-global
    registry, and its teardown stopped the server and the MinIO container without resetting that registry. Every test
    class in the module shares one reused fork under surefire's default filesystem run order, which is not stable
    across machines — so whenever that class happened to run first, S3BlobStoreRegistrarTest's "s3 should not be
    registered before register()" precondition saw inherited state and failed. The leak is now cleaned up on
    teardown, and all three registrar tests (s3, gcs, azure) scrub the registry on entry as well as exit, so the
    precondition holds regardless of what ran before them. The gcs and azure tests had the identical latent shape and
    passed only because their modules contain no equivalent leaker. Reproduced by forcing the losing order
    (-Dsurefire.runOrder=reversealphabetical) and confirmed fixed under it.

  • The new fork-PR test workflow (.github/workflows/pr-tests.yml) now reports one red per real failure instead of
    three. A latent race in ThirdPartyStreamingClientConformanceIntegrationTest surfaced on the slower
    ubuntu-latest runner: shouldDeliverMessageThenCloseWhenCloseConnectionIsSet is the one case whose server is
    configured withCloseConnection(true) (it delivers "bye" and closes immediately, by design), yet it asserted on
    Java-WebSocket's connectBlocking() boolean, which returns connectLatch.await(...) && engine.isOpen() — and that
    latch is counted down by both onWebsocketOpen and onWebsocketClose, so the deliberate close races the
    isOpen() read and the bare assert fails in ~12 ms. It now asserts the observable handshake outcome via the
    client's opened latch (onOpen always precedes any close on a successful handshake); the close, the "bye"
    payload, and the orderly close code were already verified race-free and are preserved. The product is unchanged —
    withCloseConnection(true) did exactly what it promises. Two derivative reds in the same workflow are gone too:
    the standalone-examples step and the "assert Docker-gated suites actually ran" step are now gated on the reactor
    step succeeding (steps.reactor.outcome == 'success') rather than merely !cancelled(), so a reactor failure no
    longer makes the examples build die with "Could not resolve dependencies for … mockserver-examples" (the reactor's
    install never completed) or makes the suite-ran check red on mockserver-blob-s3 with "the suite did not run"
    (S3 was only skipped because of its test-scope reactor dependency on the failed mockserver-netty, two modules
    away). The suite-ran check stays fail-closed for the case it exists for: a runner without Docker assumeTrue-skips
    the suites, Maven still exits 0, so the reactor step succeeds, the check runs, finds no report, and reds loudly.

  • Dependabot now proposes grouped Maven updates for the core mockserver reactor again. The reactor's
    mockserver/pom.xml declared <module>../examples/java</module>, a path that escaped Dependabot's
    directory: "/mockserver" scope; with no repository-root pom.xml, Dependabot re-parsed a scoped file subset,
    found no top-level pom and aborted every grouped-update run for the reactor with No pom.xml!. As a result no
    grouped Maven PR was ever created for the core modules (broken since the examples were unified in d3cfa9aaf),
    while the self-contained mockserver-maven-plugin sibling kept working, and examples/java's own dependencies had
    no coverage at all. examples/java is now removed from the reactor so /mockserver is a self-contained Maven
    directory; the examples are kept compiled and tested by a second standalone Maven invocation in CI immediately after
    the reactor install (scripts/buildkite_quick_build.sh), and their own dependencies are covered by a new
    /examples/java block in .github/dependabot.yml. (Vulnerability alerts were never affected — those come from the
    submitted dependency graph, not update proposal.)

  • The release pipeline can no longer silently overwrite the previous version's archived documentation site. Whether a
    release creates a new versioned subdomain (X-Y.mock-server.com) is fully determined by whether it is a major/minor
    release, but it was a free operator dropdown (create-versioned-site) defaulting to no. On the 7.6.0 release that
    default was left in place, so terraform's latest_version still pointed main at the previous version's bucket and
    the docs publish (aws s3 sync --delete) destroyed the 7-5.mock-server.com archive. The value is now derived
    from RELEASE_VERSION vs the previous tag in require_release_inputs (the single input-validation chokepoint every
    release script runs, before prepare.sh tags or pushes); the Buildkite input defaults to auto, and an explicit
    yes/no is honoured only as a confirmation that must agree with the derived value or the run fails closed.
    This makes both destructive mistakes impossible: a major/minor release with no (overwrites the previous archive)
    and a patch release with yes (spurious subdomain).

  • Removed a live-internet dependency from the mockserver-core unit tests. ExpectationSerializerTest
    (shouldAllowSingleOpenAPIObjectForArray and shouldAllowMixedExpectationTypesForArray) referenced the bundled
    petstore OpenAPI spec via a https://raw.githubusercontent.com/... URL; because deserializeArray() actually loads
    the spec to generate example bodies, every ordinary build fetched that URL over the internet. A transient HTTP 429
    (rate limit) from raw.githubusercontent.com failed the test — and took down the 7.6.0 release build after it had
    already tagged and pushed. Both tests now resolve the byte-identical copy from the test classpath
    (org/mockserver/openapi/openapi_petstore_example.json, the same local reference the sibling HttpStateTest and
    JsonSchemaExpectationValidatorTest already use), so the suite no longer depends on GitHub being reachable or
    un-rate-limited. The behaviour under test (a single OpenAPI object deserialising into an array of expectations with
    the correct generated bodies) is unchanged and still fully asserted.

  • The Rust client's cargo clippy gate no longer fails the build on an unchanged source tree. The
    mockserver-rust pipeline lints inside a container pulled from the floating rust:1 tag, and that tag moved to
    clippy 1.98.0, whose clippy::needless_late_init now also flags late-initialised let chains assigned across an
    if/else if sequence. resolve_platform() in mockserver-client-rust/src/launcher.rs had two such chains
    (os_name/ext, and arch), so with -D warnings the lint became a hard error and the crate stopped compiling —
    on code nobody had touched. Master build #​638 passed this exact source; build #​639, fifteen hours later on a newer
    rust:1, failed it, which also red-herring-failed an unrelated Dependabot pull request whose only change was to a
    Java dependency. Both chains are now initialising if/else expressions (a tuple for the jointly-assigned
    os_name/ext pair), exactly the rewrite clippy itself suggests; the diverging else arms still return Err(...)
    and coerce as !. Behaviour is byte-for-byte identical — same platform tokens, same error messages, same Platform
    — and was confirmed by running the fix under the real clippy 1.98.

Important

✂ PR body was truncated to here.


Configuration

📅 Schedule: (UTC)

  • Branch creation
    • At any time (no schedule defined)
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Enabled.

Rebasing: Whenever PR is behind base branch, 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 added the dependencies Pull requests that update a dependency file label Sep 15, 2026
@renovate
renovate Bot enabled auto-merge (squash) September 15, 2026 13:18
@renovate renovate Bot added the dependencies Pull requests that update a dependency file label Sep 15, 2026
@renovate
renovate Bot merged commit 9921598 into main Sep 15, 2026
27 checks passed
@renovate
renovate Bot deleted the renovate/org.mock-server-mockserver-netty-no-dependencies-8.x branch September 15, 2026 13:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant