When an expectation does not match a request, MockServer returns a 404 response. This page explains how to diagnose and fix matching issues.

 

Debugging Checklist

Follow these steps in order when a request is not matching:

If the request was served, but by a different expectation from the one you expected, or you already have its correlation ID, go straight to explaining a specific request: it names the expectation that wins and why each higher-priority one lost.

1. Is the expectation active?

Expectations expire after their configured times limit or timeToLive. Retrieve active expectations to confirm yours is still present:

Expectation[] active = mockServerClient.retrieveActiveExpectations(null);
var mockServerClient = require('mockserver-client').mockServerClient;
mockServerClient("localhost", 1080)
  .retrieveActiveExpectations()
  .then(
    function (activeExpectations) {
      console.log(JSON.stringify(activeExpectations));
    },
    function (error) {
      console.log(error);
    }
  );
from mockserver import MockServerClient

client = MockServerClient("localhost", 1080)
active = client.retrieve_active_expectations()
require 'mockserver-client'

client = MockServer::Client.new('localhost', 1080)
active = client.retrieve_active_expectations
import (
    "log"

    mockserver "github.com/mock-server/mockserver-monorepo/mockserver-client-go/v9"
)

client := mockserver.New("localhost", 1080)
active, err := client.RetrieveActiveExpectations(nil)
if err != nil {
    log.Fatal(err)
}
using MockServer.Client;

using var client = new MockServerClient("localhost", 1080);
var active = client.RetrieveActiveExpectations();
use mockserver_client::ClientBuilder;

let client = ClientBuilder::new("localhost", 1080).build().unwrap();
let active = client.retrieve_active_expectations(None).unwrap();
use MockServer\MockServerClient;

$client = new MockServerClient('localhost', 1080);
$active = $client->retrieveActiveExpectations();
curl -X PUT "http://localhost:1080/mockserver/retrieve?type=ACTIVE_EXPECTATIONS"

2. Did the request arrive at MockServer?

Verify that MockServer received the request:

HttpRequest[] requests = mockServerClient.retrieveRecordedRequests(null);
var mockServerClient = require('mockserver-client').mockServerClient;
mockServerClient("localhost", 1080)
  .retrieveRecordedRequests()
  .then(
    function (recordedRequests) {
      console.log(JSON.stringify(recordedRequests));
    },
    function (error) {
      console.log(error);
    }
  );
from mockserver import MockServerClient

client = MockServerClient("localhost", 1080)
requests = client.retrieve_recorded_requests()
require 'mockserver-client'

client = MockServer::Client.new('localhost', 1080)
requests = client.retrieve_recorded_requests
import (
    "log"

    mockserver "github.com/mock-server/mockserver-monorepo/mockserver-client-go/v9"
)

client := mockserver.New("localhost", 1080)
requests, err := client.RetrieveRecordedRequests(nil)
if err != nil {
    log.Fatal(err)
}
using MockServer.Client;

using var client = new MockServerClient("localhost", 1080);
var requests = client.RetrieveRecordedRequests();
use mockserver_client::ClientBuilder;

let client = ClientBuilder::new("localhost", 1080).build().unwrap();
let requests = client.retrieve_recorded_requests(None).unwrap();
use MockServer\MockServerClient;

$client = new MockServerClient('localhost', 1080);
$requests = $client->retrieveRecordedRequests();
curl -X PUT "http://localhost:1080/mockserver/retrieve?type=REQUESTS"

3. Check the match failure reason

MockServer logs an EXPECTATION_NOT_MATCHED event for every expectation that was compared. Each event includes a because section explaining which fields matched and which did not:

curl -X PUT "http://localhost:1080/mockserver/retrieve?type=LOGS"

When a request matches nothing and more than 1,000 expectations are stored, MockServer compares it with the expectations that share its method and path (plus any whose method or path is not a plain value) and with at most 1,000 others, in the order it would serve them, so a flood of unmatched requests stays cheap. The log then has an event only for the expectations it compared, and the closest-match entry ends with only N of M expectations were checked, so a closer one may exist.

A request writes at most 50 "didn't match" events, set by maxLoggedMatchFailures. Every expectation is still compared, so the closest-match entry is the same, and when events were left out it says how many, for example 12 further did-not-match entries were not logged (maxLoggedMatchFailures). Set maxLoggedMatchFailures to -1 to log every comparison, or lower it if you store many expectations and expect many unmatched requests.

In the dashboard UI, look for log entries with the pink/rose colour. Click the ... to expand the "because" section, which shows each field result colour-coded green (matched) or red (didn't match).

4. Enable full match diagnostics

By default, MockServer stops comparing fields at the first mismatch (matchersFailFast=true). To see all mismatching fields at once, disable fail-fast:

ConfigurationProperties.matchersFailFast(false);
# System property
-Dmockserver.matchersFailFast=false

# Environment variable
MOCKSERVER_MATCHERS_FAIL_FAST=false

This shows every field that does not match in a single log entry, making it much easier to identify all issues at once rather than fixing them one at a time.

5. Look for the closest match

When no expectation matches, MockServer logs a closest match summary showing which expectation was most similar to the request and how many fields matched (e.g., "matched 14/16 fields"). This immediately tells you which expectation to investigate.

 

Common Pitfalls

These are the most frequent reasons expectations fail to match:

Trailing slash in path

A path of /api/users does not match /api/users/. Ensure your expectation path matches exactly, or use a regex:

// Exact match - will NOT match "/api/users/"
.withPath("/api/users")

// Match with or without trailing slash
.withPath("/api/users/?")

MockServer will show a HINT in the match failure when it detects a trailing slash mismatch.

Content-Type charset mismatch

Many HTTP clients send Content-Type: application/json; charset=utf-8 but expectations often specify just application/json. This is a header value mismatch.

Solutions:

  • Include the charset in your expectation: .withHeader("Content-Type", "application/json; charset=utf-8")
  • Use a substring header matcher
  • Omit the Content-Type header from your expectation (unspecified headers match anything)

MockServer will show a HINT when it detects this pattern.

JSON body matching semantics

By default, JSON body matching uses ONLY_MATCHING_FIELDS mode:

  • Extra fields in the request body are ignored (the expectation is a subset match)
  • Missing fields (a field specified in the matcher but absent from the request body) cause a failure
  • Array order does not matter
  • Extra array items are ignored

If you need an exact match, use STRICT mode:

// Subset match (default) - extra fields OK
.withBody(json("{\"name\": \"foo\"}", MatchType.ONLY_MATCHING_FIELDS))

// Strict match - no extra fields allowed
.withBody(json("{\"name\": \"foo\"}", MatchType.STRICT))

Regex metacharacters in paths

Paths are matched as regular expressions. Characters like ., *, +, ?, (, ), [, { have special meaning in regex.

For example, /api/v1.0/users will also match /api/v1X0/users because . matches any character. Use \. for a literal dot:

// This matches "/api/v1X0/users" too
.withPath("/api/v1.0/users")

// This only matches "/api/v1.0/users"
.withPath("/api/v1\\.0/users")

MockServer will show a HINT when it detects unescaped dots in paths.

Header name case sensitivity

HTTP header names are matched case-insensitively (per HTTP spec), but header values are case-sensitive. For example:

// Header name "content-type" matches "Content-Type" - OK
// Header value "Application/JSON" does NOT match "application/json"
.withHeader("Content-Type", "application/json")

Query parameter encoding

Query parameters are decoded before matching. ?name=hello%20world is matched against the decoded value hello world:

// Matches ?name=hello%20world and ?name=hello+world
.withQueryStringParameter("name", "hello world")

Body type confusion

Using the wrong body matcher type is a common mistake:

// This is an EXACT string match - entire body must match this text exactly
.withBody("{\"name\": \"foo\"}")

// This is a JSON subset match - only specified fields must be present
.withBody(json("{\"name\": \"foo\"}"))

// This is a regex match
.withBody(regex(".*foo.*"))

If your JSON body isn't matching, check that you are using json() and not a plain string body matcher.

 

Reading the "because" Output

When a request does not match an expectation, the log entry includes a "because" section. Here is an annotated example:

method matched                        ← GET matched GET
path didn't match:                    ← path comparison failed

  string or regex match failed        ← the matcher type used
  expected: /api/users                ← what the expectation specified
  found: /api/user                    ← what was in the request
HINT: trailing slash mismatch ...     ← actionable suggestion (when applicable)
body matched                          ← body was not checked (no body matcher)
headers matched                       ← all expected headers were found
cookies matched                       ← all expected cookies were found

Fields are listed in matching order. When matchersFailFast=true (default), only fields up to and including the first failure are shown. Set matchersFailFast=false to see all fields.

 

Debugging Configuration Properties

Property Default Description
matchersFailFast true When true, matching stops at the first non-matching field. Set to false to see all mismatching fields in a single log entry.
detailedMatchFailures true When true, match failure log entries include detailed explanations of why each field did not match (expected vs actual values).
logLevel INFO At INFO (default), all match results are logged with full detail. Set to TRACE for internal matcher-level diagnostics — see Debugging Issues → Logging Levels for how to set the log level and what each level reports.

For full configuration details, see the Configuration page.

 

Programmatic Mismatch Debugging

MockServer provides a dedicated PUT /mockserver/debugMismatch endpoint that analyzes why a request does not match any active expectations. It returns structured JSON showing per-expectation, per-field match results — including the closest match.

String result = mockServerClient.debugMismatch(
    request()
        .withMethod("GET")
        .withPath("/api/users")
        .withHeader("Content-Type", "application/json")
);
System.out.println(result);
var mockServerClient = require('mockserver-client').mockServerClient;

const result = await mockServerClient("localhost", 1080).debugMismatch({
  "method": "GET",
  "path": "/api/users",
  "headers": { "Content-Type": ["application/json"] }
});
console.log(result.closestMatch, result.results);
from mockserver import MockServerClient, HttpRequest

client = MockServerClient("localhost", 1080)
result = client.debug_mismatch(
    HttpRequest(method="GET", path="/api/users", headers={"Content-Type": ["application/json"]})
)
for expectation in result.results:
    print(expectation.expectation_id, expectation.differences)
require 'mockserver-client'

client = MockServer::Client.new('localhost', 1080)
result = client.debug_mismatch(
  MockServer::HttpRequest.new(method: 'GET', path: '/api/users',
                              headers: { 'Content-Type' => ['application/json'] })
)
result.results.each { |expectation| puts "#{expectation.expectation_id} #{expectation.differences}" }
import (
    "fmt"
    "log"

    mockserver "github.com/mock-server/mockserver-monorepo/mockserver-client-go/v9"
)

client := mockserver.New("localhost", 1080)
report, err := client.DebugMismatch(
    mockserver.Request().
        Method("GET").
        Path("/api/users").
        Header("Content-Type", "application/json"),
)
if err != nil {
    log.Fatal(err)
}
for _, result := range report.Results {
    fmt.Println(result.ExpectationID, result.Differences)
}
using MockServer.Client;
using MockServer.Client.Models;

using var client = new MockServerClient("localhost", 1080);
MismatchDebugResult result = client.DebugMismatch(
    HttpRequest.Request()
        .WithMethod("GET")
        .WithPath("/api/users")
        .WithHeader("Content-Type", "application/json")
);
Console.WriteLine($"closest match: {result.ClosestMatch?.ExpectationId}");
foreach (var expectation in result.Results)
{
    Console.WriteLine($"{expectation.ExpectationId} matches: {expectation.Matches}");
}
use mockserver_client::{ClientBuilder, HttpRequest};

let client = ClientBuilder::new("localhost", 1080).build().unwrap();
let report = client
    .debug_mismatch(
        HttpRequest::new()
            .method("GET")
            .path("/api/users")
            .header("Content-Type", "application/json"),
    )
    .unwrap();
for result in &report.results {
    println!("{:?} {:?}", result.expectation_id, result.differences);
}
use MockServer\MockServerClient;
use MockServer\HttpRequest;

$client = new MockServerClient('localhost', 1080);
$result = $client->debugMismatch(
    HttpRequest::request()
        ->method('GET')
        ->path('/api/users')
        ->header('Content-Type', 'application/json')
);
echo 'closest match: ' . $result->closestMatch?->expectationId . PHP_EOL;
foreach ($result->results as $expectation) {
    echo $expectation->expectationId . ' matches: ' . var_export($expectation->matches, true) . PHP_EOL;
}
curl -X PUT "http://localhost:1080/mockserver/debugMismatch" \
  -H "Content-Type: application/json" \
  -d '{
    "method": "GET",
    "path": "/api/users",
    "headers": {
      "Content-Type": ["application/json"]
    }
  }' 

Response Format

The response is a JSON object with:

  • totalExpectations — the number of active expectations compared
  • closestMatch — which expectation had the fewest differences
  • results — per-expectation match analysis with field-level differences
{
  "totalExpectations": 2,
  "closestMatch": {
    "expectationId": "abc-123",
    "matchedFields": 18,
    "totalFields": 19
  },
  "results": [
    {
      "expectationId": "abc-123",
      "expectationPath": "/api/users",
      "expectationMethod": "POST",
      "matches": false,
      "matchedFieldCount": 18,
      "totalFieldCount": 19,
      "differences": {
        "method": [
          "string or regex match failed expected: POST found: GET"
        ]
      }
    }
  ]
}

Worked Example: "My test is getting 404"

When your test receives a 404, use the debug endpoint to find out exactly why:

// 1. Set up your expectation
mockServerClient.when(
    request().withMethod("POST").withPath("/api/users")
        .withBody(json("{\"name\": \"foo\"}"))
).respond(response().withStatusCode(201));

// 2. Your test sends a request that gets 404 - why?
// 3. Debug the mismatch
String analysis = mockServerClient.debugMismatch(
    request()
        .withMethod("GET")           // oops - wrong method!
        .withPath("/api/users")
        .withBody("{\"name\": \"foo\"}")
);

// The response will show:
// - method didn't match: expected POST, found GET
// - matchedFieldCount: 18/19 (all fields matched except method)
var mockServerClient = require('mockserver-client').mockServerClient;
var client = mockServerClient("localhost", 1080);

// 1. Set up your expectation
await client.mockAnyResponse({
  "httpRequest": {
    "method": "POST",
    "path": "/api/users",
    "body": { "type": "JSON", "json": { "name": "foo" } }
  },
  "httpResponse": { "statusCode": 201 }
});

// 2. Your test sends a request that gets 404 - why?
// 3. Debug the mismatch
const analysis = await client.debugMismatch({
  "method": "GET",              // oops - wrong method!
  "path": "/api/users",
  "body": "{\"name\": \"foo\"}"
});
// the expectation's differences show method: expected POST, found GET
for (const expectation of analysis.results || []) {
  console.log(expectation.expectationId, expectation.differences);
}
from mockserver import MockServerClient, HttpRequest, HttpResponse, Body

client = MockServerClient("localhost", 1080)

# 1. Set up your expectation
client.when(
    HttpRequest(method="POST", path="/api/users", body=Body(type="JSON", json={"name": "foo"}))
).respond(
    HttpResponse(status_code=201)
)

# 2. Your test sends a request that gets 404 - why?
# 3. Debug the mismatch
analysis = client.debug_mismatch(
    HttpRequest(method="GET", path="/api/users", body='{"name": "foo"}')  # oops - wrong method!
)
# the expectation's differences show method: expected POST, found GET
for expectation in analysis.results:
    print(expectation.expectation_id, expectation.differences)
require 'mockserver-client'
include MockServer

client = MockServer::Client.new('localhost', 1080)

# 1. Set up your expectation
client.when(
  HttpRequest.new(method: 'POST', path: '/api/users', body: Body.json('{"name": "foo"}'))
).respond(
  HttpResponse.new(status_code: 201)
)

# 2. Your test sends a request that gets 404 - why?
# 3. Debug the mismatch
analysis = client.debug_mismatch(
  HttpRequest.new(method: 'GET', path: '/api/users', body: '{"name": "foo"}') # oops - wrong method!
)
# the expectation's differences show method: expected POST, found GET
analysis.results.each { |expectation| puts "#{expectation.expectation_id} #{expectation.differences}" }
import (
    "fmt"
    "log"

    mockserver "github.com/mock-server/mockserver-monorepo/mockserver-client-go/v9"
)

client := mockserver.New("localhost", 1080)

// 1. Set up your expectation
client.When(
    mockserver.Request().Method("POST").Path("/api/users").JSONBody(`{"name":"foo"}`),
).Respond(
    mockserver.Response().StatusCode(201),
)

// 2. Your test sends a request that gets 404 - why?
// 3. Debug the mismatch
report, err := client.DebugMismatch(
    mockserver.Request().
        Method("GET"). // oops - wrong method!
        Path("/api/users").
        Body(`{"name": "foo"}`),
)
if err != nil {
    log.Fatal(err)
}

// The report will show:
// - method didn't match: expected POST, found GET
for _, result := range report.Results {
    fmt.Println(result.ExpectationID, result.Differences["method"])
}
using MockServer.Client;
using MockServer.Client.Models;

using var client = new MockServerClient("localhost", 1080);

// 1. Set up your expectation
client.When(
    HttpRequest.Request().WithMethod("POST").WithPath("/api/users").WithJsonBody("{\"name\": \"foo\"}")
).Respond(
    HttpResponse.Response().WithStatusCode(201)
);

// 2. Your test sends a request that gets 404 - why?
// 3. Debug the mismatch
MismatchDebugResult analysis = client.DebugMismatch(
    HttpRequest.Request()
        .WithMethod("GET")           // oops - wrong method!
        .WithPath("/api/users")
        .WithBody("{\"name\": \"foo\"}")
);

// analysis.Results shows:
// - method didn't match: expected POST, found GET
// - MatchedFieldCount: 18/19 (all fields matched except method)
use mockserver_client::{ClientBuilder, HttpRequest, HttpResponse};

let client = ClientBuilder::new("localhost", 1080).build().unwrap();

// 1. Set up your expectation
client.when(
    HttpRequest::new().method("POST").path("/api/users").json_body(serde_json::json!({"name": "foo"}))
).respond(
    HttpResponse::new().status_code(201)
).unwrap();

// 2. Your test sends a request that gets 404 - why?
// 3. Debug the mismatch
let report = client.debug_mismatch(
    HttpRequest::new()
        .method("GET") // oops - wrong method!
        .path("/api/users")
        .body(r#"{"name": "foo"}"#)
).unwrap();

// The report will show:
// - method didn't match: expected POST, found GET
for result in &report.results {
    println!("{:?}", result.differences.get("method"));
}
use MockServer\MockServerClient;
use MockServer\HttpRequest;
use MockServer\HttpResponse;

$client = new MockServerClient('localhost', 1080);

// 1. Set up your expectation
$client->when(
    HttpRequest::request()->method('POST')->path('/api/users')->jsonBody(['name' => 'foo'])
)->respond(
    HttpResponse::response()->statusCode(201)
);

// 2. Your test sends a request that gets 404 - why?
// 3. Debug the mismatch
$analysis = $client->debugMismatch(
    HttpRequest::request()
        ->method('GET')              // oops - wrong method!
        ->path('/api/users')
        ->body('{"name": "foo"}')
);

// $analysis->results shows:
// - method didn't match: expected POST, found GET
// - matchedFieldCount: 18/19 (all fields matched except method)
# 1. Set up your expectation
curl -v -X PUT "http://localhost:1080/mockserver/expectation" -d '{
  "httpRequest": {
    "method": "POST",
    "path": "/api/users",
    "body": { "type": "JSON", "json": { "name": "foo" } }
  },
  "httpResponse": { "statusCode": 201 }
}'

# 2. Debug why a GET request does not match
curl -X PUT "http://localhost:1080/mockserver/debugMismatch" -d '{
  "method": "GET",
  "path": "/api/users",
  "body": "{\"name\": \"foo\"}"
}' 

This endpoint is also available via the MCP debug_request_mismatch tool for AI-assisted debugging.

 

Suggested Fixes for Unmatched Requests

When a request got a 404 because an expectation was almost right, MockServer can suggest the smallest change to that expectation that would make it match. Nothing is changed until you choose to apply the fix.

In the dashboard: open Explain Unmatched Requests, select Preview Fix next to one of the closest expectations, check the changes and the request matcher before and after, then select Apply Fix. Apply re-reads the expectation first: if anything the fix changes was edited since the explanation was computed, the fix is refused rather than overwriting the edit.

Over REST: add "suggestFixes": true to the PUT /mockserver/explainUnmatched body. Each of the five closest non-matching expectations then carries a suggestedFix:

curl -X PUT "http://localhost:1080/mockserver/explainUnmatched" \
  -H "Content-Type: application/json" \
  -d '{ "limit": 5, "suggestFixes": true }'
"suggestedFix" : {
  "patch" : [
    { "op" : "test", "path" : "/httpRequest/path", "value" : "/api/order" },
    { "op" : "replace", "path" : "/httpRequest/path", "value" : "/api/orders" },
    { "op" : "test", "path" : "/httpRequest/headers/X-Tenant", "value" : [ "blue" ] },
    { "op" : "replace", "path" : "/httpRequest/headers/X-Tenant", "value" : [ "green" ] }
  ],
  "patchedFields" : [ "path", "headers" ],
  "unpatchedFields" : [ ],
  "matchesAfterPatch" : true
}
  • patch is a standard JSON Patch of the expectation as PUT /mockserver/retrieve?type=active_expectations returns it. Each change is guarded by a test of the current value (for an addition, of the object it adds to), so applying it fails if anything it changes has been edited since; edits to other parts of the expectation are kept. To apply it yourself, patch the retrieved expectation and send the result to PUT /mockserver/expectation, which updates the expectation with the same id.
  • Only the fields that differ are changed. For headers, query parameters and cookies, only the entries the request contradicts are changed (or removed if the request does not send them). For a JSON body matched on its listed fields (the default), only the fields the request contradicts are changed, fields the request lacks are removed, and placeholders such as ${json-unit.any-string} are kept; a STRICT JSON body, or any other kind of body, is replaced by the request's body.
  • unpatchedFields lists anything that could not be fixed automatically and why — for example a client certificate or JWT matcher, a body the log kept only part of (maxLoggedBodyBytes), or a value the log redacts (redactSecretsInLog), which is never copied into a patch.
  • matchesAfterPatch says whether the patched expectation matches the request. When it is false, the fields in unpatchedFields still need editing by hand.

A suggested fix makes the expectation match this request: replacing a regex path or a placeholder value with the literal value from the request can make it narrower than you intended, so check the preview before applying.

AI assistants can ask for the same fixes through the MCP suggest_expectation_fix tool.

 

Retrieving Logs by Correlation ID

Every incoming HTTP request is assigned a unique correlation ID. All log entries for that request's lifecycle (received, match attempts, response) share this ID. This is invaluable when debugging a specific request among many — you can isolate exactly what happened.

Finding the Correlation ID

The correlation ID appears in:

  • Dashboard UI — as a small chip on grouped log entries (click to copy)
  • Log text — in the prefix of each log message
  • Typed log entries — as the correlationId field when using format=LOG_ENTRIES

Retrieving by Correlation ID

Once you have a correlation ID, retrieve all log entries for that request:

retrieveLogsByCorrelationId throws, rather than returning the error body as a string, if the control-plane request itself gets a response status of 400 or more: IllegalArgumentException for a 400, AuthenticationException for a 401, and ClientException for any other status (including a 500 from an internal MockServer fault) — wrap the call accordingly.

try {
    String logs = mockServerClient.retrieveLogsByCorrelationId("abc-123-def-456");
} catch (ClientException e) {
    // the control-plane request itself failed (e.g. a 500 inside MockServer)
} catch (IllegalArgumentException | AuthenticationException e) {
    // the request was rejected (400) or not authenticated (401)
}
// no typed correlationId filter - call the REST endpoint
const response = await fetch(
  "http://localhost:1080/mockserver/retrieve?type=LOGS&correlationId=abc-123-def-456",
  { method: "PUT" }
);
console.log(await response.text());
import requests

# no typed correlationId filter - call the REST endpoint
response = requests.put(
    "http://localhost:1080/mockserver/retrieve",
    params={"type": "LOGS", "correlationId": "abc-123-def-456"},
)
print(response.text)
require 'net/http'

# no typed correlationId filter - call the REST endpoint
uri = URI('http://localhost:1080/mockserver/retrieve?type=LOGS&correlationId=abc-123-def-456')
response = Net::HTTP.start(uri.host, uri.port) { |http| http.request(Net::HTTP::Put.new(uri)) }
puts response.body
import (
    "log"
    "net/http"
)

// no typed correlationId filter - call the REST endpoint
url := "http://localhost:1080/mockserver/retrieve?type=LOGS&correlationId=abc-123-def-456"
req, _ := http.NewRequest("PUT", url, nil)
resp, err := http.DefaultClient.Do(req)
if err != nil {
    log.Fatal(err)
}
defer resp.Body.Close()
using System.Net.Http;

// no typed correlationId filter - call the REST endpoint
using var http = new HttpClient();
var response = await http.PutAsync(
    "http://localhost:1080/mockserver/retrieve?type=LOGS&correlationId=abc-123-def-456", null);
Console.WriteLine(await response.Content.ReadAsStringAsync());
use reqwest::blocking::Client;

// no typed correlationId filter - call the REST endpoint
let logs = Client::new()
    .put("http://localhost:1080/mockserver/retrieve?type=LOGS&correlationId=abc-123-def-456")
    .send()
    .unwrap()
    .text()
    .unwrap();
println!("{}", logs);
use GuzzleHttp\Client;

// no typed correlationId filter - call the REST endpoint
$http = new Client();
$response = $http->put('http://localhost:1080/mockserver/retrieve', [
    'query' => ['type' => 'LOGS', 'correlationId' => 'abc-123-def-456'],
]);
echo (string) $response->getBody();
curl -X PUT "http://localhost:1080/mockserver/retrieve?type=LOGS&correlationId=abc-123-def-456"

The correlation ID is visible in the dashboard UI as a small chip on grouped log entries. Click it to copy the ID.

 

Explaining Why a Request Matched One Expectation and Not Another

When a request was served by the wrong expectation, or by none, PUT /mockserver/explain?correlationId=<id> tells you why. It takes the recorded request with that correlation ID and checks it again against your current expectations, in the same order MockServer uses when a request arrives (highest priority first, then oldest first). It returns:

  • matchedExpectation — the expectation that would serve the request now (absent if none would; closestExpectationId then names the closest one)
  • skippedExpectations — every expectation checked before it, each with a reason, a plain-language explanation and, for field differences, the same differences and remediation hints that explainUnmatched returns
  • shadowedExpectations — lower-priority expectations that also match but are never reached
  • atArrival — how the request was actually handled when it arrived (MATCHED with the expectation ID, NO_MATCH, FORWARDED_WITHOUT_EXPECTATION, RESPONDED_WITHOUT_EXPECTATION or UNKNOWN), and sameOutcomeAsAtArrival. When the request was forwarded and the forward failed (MockServer answered 502), atArrival.forwardFailure gives the reason; the outcome is then MATCHED with the forward expectation's ID, or FORWARDED_WITHOUT_EXPECTATION if the request was proxied without an expectation
  • expectationsChangedSince — true if any expectation was added, changed or removed since the request arrived
ReasonWhy the expectation was passed over
FIELDS_DIFFERThe request differs on one or more fields (method, path, headers, body, …)
NOT_OPERATORThe expectation's request matcher is wrapped in not(...) and the request matches everything inside it, so the expectation excludes this request
NAMESPACEThe expectation belongs to a different namespace from the request
INACTIVEIts Times are used up or its time to live has expired
SCENARIO_STATEIt needs its scenario to be in a different state from the current one
PERCENTAGEIt matches, but is only served for a percentage of requests
CONVERSATIONThe request matches, but its LLM conversation does not

The answer is worked out again when you ask, not recorded when the request arrived, so it costs nothing on normal requests. This also means it reflects the expectations, scenario states and remaining Times as they are now: an expectation limited to once() that served the request will usually show as gone (atArrival.expectationStillStored is false). If the request body was cut short in the log by maxLoggedBodyBytes, request.bodyTruncated is true and body matching is checked against the shortened copy. Request values in the output are redacted in the same way as the log. At most 500 expectations are checked; truncated says when that limit was reached. A correlation ID that is not in the log (never received, cleared, or dropped once maxLogEntries was reached) returns 404.

In the dashboard, each group of log messages for a request has an Explain button next to its correlation ID, which shows the same answer: the matched expectation, the skipped ones with their reasons and differences, and the shadowed ones. Every client has a typed method for it: explain in Java, JavaScript, Python, Ruby, Rust and PHP, and Explain in Go and .NET.

RequestExplanation explanation = new MockServerClient("localhost", 1080)
    .explain("abc-123-def-456");

if (explanation.matchedExpectation() != null) {
    System.out.println("served now by " + explanation.matchedExpectation().expectationId());
}
for (RequestExplanation.ExplainedExpectation skipped : explanation.skippedExpectations()) {
    System.out.println(skipped.expectationId() + " " + skipped.reason() + ": " + skipped.explanation());
}
// an unknown or evicted correlation ID throws a ClientException
var mockServerClient = require('mockserver-client').mockServerClient;

const explanation = await mockServerClient("localhost", 1080).explain("abc-123-def-456");
if (explanation.matchedExpectation) {
  console.log("served now by " + explanation.matchedExpectation.expectationId);
}
for (const skipped of explanation.skippedExpectations || []) {
  console.log(skipped.expectationId + " " + skipped.reason + ": " + skipped.explanation);
}
// an unknown or evicted correlation ID rejects with the server's reason
from mockserver import MockServerClient

client = MockServerClient("localhost", 1080)
explanation = client.explain("abc-123-def-456")
if explanation.matched_expectation:
    print("served now by", explanation.matched_expectation.expectation_id)
for skipped in explanation.skipped_expectations:
    print(skipped.expectation_id, skipped.reason, skipped.explanation)
# an unknown or evicted correlation ID raises MockServerError
require 'mockserver-client'

client = MockServer::Client.new('localhost', 1080)
explanation = client.explain('abc-123-def-456')
if explanation.matched_expectation
  puts "served now by #{explanation.matched_expectation.expectation_id}"
end
explanation.skipped_expectations.each do |skipped|
  puts "#{skipped.expectation_id} #{skipped.reason}: #{skipped.explanation}"
end
# an unknown or evicted correlation ID raises MockServer::Error
import (
    "fmt"
    "log"

    mockserver "github.com/mock-server/mockserver-monorepo/mockserver-client-go/v9"
)

client := mockserver.New("localhost", 1080)
explanation, err := client.Explain("abc-123-def-456")
if err != nil {
    log.Fatal(err) // an unknown or evicted correlation ID returns an error
}
if explanation.MatchedExpectation != nil {
    fmt.Println("served now by", explanation.MatchedExpectation.ExpectationID)
}
for _, skipped := range explanation.SkippedExpectations {
    fmt.Printf("%s %s: %s\n", skipped.ExpectationID, skipped.Reason, skipped.Explanation)
}
using MockServer.Client;
using MockServer.Client.Models;

using var client = new MockServerClient("localhost", 1080);
RequestExplanation explanation = client.Explain("abc-123-def-456");

if (explanation.MatchedExpectation != null)
{
    Console.WriteLine($"served now by {explanation.MatchedExpectation.ExpectationId}");
}
foreach (var skipped in explanation.SkippedExpectations)
{
    Console.WriteLine($"{skipped.ExpectationId} {skipped.Reason}: {skipped.Explanation}");
}
// an unknown or evicted correlation ID throws a MockServerClientException
use mockserver_client::ClientBuilder;

let client = ClientBuilder::new("localhost", 1080).build().unwrap();
// an unknown or evicted correlation ID returns Err(Error::NotFound(..))
let explanation = client.explain("abc-123-def-456").unwrap();
if let Some(matched) = &explanation.matched_expectation {
    println!("served now by {:?}", matched.expectation_id);
}
for skipped in &explanation.skipped_expectations {
    println!("{:?} {:?}: {:?}", skipped.expectation_id, skipped.reason, skipped.explanation);
}
use MockServer\MockServerClient;

$client = new MockServerClient('localhost', 1080);
$explanation = $client->explain('abc-123-def-456');

if ($explanation->matchedExpectation !== null) {
    echo 'served now by ' . $explanation->matchedExpectation->expectationId . PHP_EOL;
}
foreach ($explanation->skippedExpectations as $skipped) {
    echo "{$skipped->expectationId} {$skipped->reason}: {$skipped->explanation}" . PHP_EOL;
}
// an unknown or evicted correlation ID throws an InvalidRequestException
curl -X PUT "http://localhost:1080/mockserver/explain?correlationId=abc-123-def-456"

For example, a request GET /orders with header X-Tenant: silver, where a priority-10 expectation requires X-Tenant: gold and a priority-0 expectation matches any GET /orders:

{
  "correlationId": "abc-123-def-456",
  "recomputed": true,
  "request": { "method": "GET", "path": "/orders" },
  "atArrival": { "outcome": "MATCHED", "expectationId": "catch-all", "expectationStillStored": true },
  "expectationsChangedSince": false,
  "sameOutcomeAsAtArrival": true,
  "matchedExpectation": { "expectationId": "catch-all", "position": 2, "priority": 0 },
  "skippedExpectations": [
    {
      "expectationId": "gold-tenant",
      "position": 1,
      "priority": 10,
      "reason": "FIELDS_DIFFER",
      "explanation": "the request differs on: headers",
      "differences": { "headers": [ "..." ] }
    }
  ],
  "shadowedExpectations": [],
  "totalExpectations": 2,
  "evaluatedExpectations": 2,
  "truncated": false
}

Fields are shortened here; each expectation entry also carries expectationPath, expectationMethod and the field counts that explainUnmatched returns.

AI assistants can ask the same question through the MCP explain_request tool.

 

Plain-language summary from your own LLM (optional)

If you have configured a runtime LLM backend and turned on llmExplainNarrationEnabled (off by default), add narrate=true to PUT /mockserver/explain or PUT /mockserver/explainUnmatched to get a short summary in plain words, for example "expectation gold-tenant wants X-Tenant: gold but the request sent silver, so the catch-all served it":

curl -X PUT "http://localhost:1080/mockserver/explain?correlationId=abc-123-def-456&narrate=true"

The response is the same as without narrate, with one extra narration field:

"narration": {
  "available": true,
  "aiGenerated": true,
  "label": "AI-generated by the configured LLM and can be wrong; the other fields of this response are the deterministic facts",
  "provider": "OPENAI",
  "model": "gpt-4o-mini",
  "text": "..."
}
  • Off unless you ask, twice. Nothing is sent to an LLM unless whoever runs MockServer sets llmExplainNarrationEnabled=true (for example -Dmockserver.llmExplainNarrationEnabled=true or MOCKSERVER_LLM_EXPLAIN_NARRATION_ENABLED=true) and the caller adds narrate=true; a backend configured for other features is not enough. Nothing is ever sent while a request is being served or answered with a 404.
  • The facts stay authoritative. The summary is written by an LLM and can be wrong, so it is labelled as AI-generated; every other field is MockServer's own deterministic answer.
  • Credentials are removed first. Before the explanation is sent, credential values from the request and your expectations (authorization headers, cookies, API keys, tokens, passwords and other credential fields, JWTs and URL passwords) are replaced with ***REDACTED***, whether or not redactSecretsInLog is on, and long values are shortened.
  • Never an error. With narration turned off, no LLM backend configured, or when the LLM fails, returns nothing or does not answer within llmRequestTimeoutMillis plus two seconds, you still get the full explanation, and narration has "available": false and a reason. At most two summaries are produced at once; a third request is answered straight away with the reason.
 

Linting Expectations

Some expectations can never match, however the request is sent: one evaluated earlier catches every request it would match, a path is missing its leading /, or times allows 0 matches. Lint finds these before you spend time debugging a 404. It is advisory: it never rejects or changes an expectation.

Lint runs in four places:

  • When you create expectations — PUT /mockserver/expectation (and every client's create or upsert call) still stores the expectations, but when a quick check finds a problem the response carries an x-mockserver-lint-warnings header with the number of problems, and each one is written to the log as a WARN. This quick check looks at each expectation on its own; it does not look for shadowing.
  • When an initialisation file loads — expectations loaded from initializationJsonPath (--init) are checked once each time the file loads, at start-up and again on every reload when the file is watched (--watch). Every expectation is still loaded; any problems, including an expectation shadowed by one earlier in the same file and an entry whose id a later entry repeats (the load keeps the last), are written to the log as one WARN that names the file and lists the first 20 problems; a reload that finds the same problems does not repeat them. An expectation without an id is named by its method and path.
  • On demand — PUT /mockserver/lint with an empty body checks all your active expectations, including for shadowing. Send expectations in the body (one, or an array) to check them without storing them. It only reads, so a client with the read role may call it.
  • In the dashboard — the Lint button on the dashboard's Active Expectations panel lists the same findings, and clicking an expectation id (including the one that shadows another) opens it in the Mocks composer. Creating an expectation in the Mocks composer, importing Expectation JSON, registering a generated stub, capturing a request as a mock or applying a suggested fix shows a Lint Warnings badge when the create found problems.
  • From the command line — mockserver lint expectations.json checks expectation files with no server running (see command line subcommands).
  • From an AI assistant — the MCP lint_expectations tool runs the on-demand check.
RuleWhat it finds
SHADOWEDAn expectation evaluated earlier (higher priority, or the same priority and created earlier) matches every request this one matches and never runs out, so this one is never used. Give this one a higher priority, or make the other more specific.
TIMES_ZERO, TIME_TO_LIVE_ZERO, PERCENTAGE_ZEROtimes, timeToLive or percentage is 0, so the expectation is never served
UNREACHABLE_RESPONSESA list of responses that times never reaches: with times of 2, the third response is never sent
PATH_MISSING_LEADING_SLASHusers/1 instead of /users/1
PATH_INCLUDES_SCHEME_AND_HOSThttps://api.example.com/users: the path is matched without the scheme and host
PATH_INCLUDES_QUERY_STRING/search?q=shoes: use queryStringParameters instead
PATH_GLOB_WILDCARD/api/*: paths are regular expressions, so use /api/.*
PATH_TEMPLATE_WITHOUT_PATH_PARAMETERS/users/{id} without pathParameters, which matches only the literal text {id}
METHOD_TYPO, HEADER_NAME_TYPOA method or header name close to a standard one, such as GTE for GET, Authorisation for Authorization or content_type for Content-Type
STRICT_JSON_BODY_WITH_ARRAYSInformation only: STRICT JSON matching of a body with arrays needs the same order and no extra items
INVALID_EXPECTATION, DUPLICATE_IDWhen checking expectations you send or files: an entry that would be rejected, or two entries with the same id, of which only the last is kept, both in expectations you send and in initialisation files

The shadowing check is careful: it reports an expectation only when it can prove the earlier one always wins, so it may miss some cases but does not report an expectation that can still be used. It never fetches an OpenAPI specification; OpenAPI entries in the body are skipped and counted. To keep it quick on very large expectation sets it compares at most 100,000 pairs and stops a regular expression that takes too long; shadowCheckTruncated is true when either limit was reached.

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

// no typed method in the Java client - call the REST endpoint
HttpClient http = HttpClient.newHttpClient();
URI lint = URI.create("http://localhost:1080/mockserver/lint");

// lint the active expectations
HttpResponse<String> response = http.send(
    HttpRequest.newBuilder(lint).PUT(HttpRequest.BodyPublishers.noBody()).build(),
    HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());

// lint expectations without storing them
String expectations = """
    [
        { "httpRequest": { "method": "GET", "path": "/users/.*" }, "httpResponse": { "statusCode": 200 } },
        { "httpRequest": { "method": "GET", "path": "/users/1" }, "httpResponse": { "statusCode": 404 } }
    ]""";
response = http.send(
    HttpRequest.newBuilder(lint)
        .header("Content-Type", "application/json")
        .PUT(HttpRequest.BodyPublishers.ofString(expectations))
        .build(),
    HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
// no typed method in the Node client - call the REST endpoint
// lint the active expectations
const response = await fetch("http://localhost:1080/mockserver/lint", { method: "PUT" });
console.log(await response.json());

// lint expectations without storing them
const checked = await fetch("http://localhost:1080/mockserver/lint", {
  method: "PUT",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify([
    { httpRequest: { method: "GET", path: "/users/.*" }, httpResponse: { statusCode: 200 } },
    { httpRequest: { method: "GET", path: "/users/1" }, httpResponse: { statusCode: 404 } }
  ])
});
console.log(await checked.json());
import requests

# no typed method - call the REST endpoint
# lint the active expectations
response = requests.put("http://localhost:1080/mockserver/lint")
print(response.json())

# lint expectations without storing them
response = requests.put(
    "http://localhost:1080/mockserver/lint",
    json=[
        {"httpRequest": {"method": "GET", "path": "/users/.*"}, "httpResponse": {"statusCode": 200}},
        {"httpRequest": {"method": "GET", "path": "/users/1"}, "httpResponse": {"statusCode": 404}},
    ],
)
print(response.json())
require 'net/http'
require 'json'

# no typed method - call the REST endpoint
uri = URI('http://localhost:1080/mockserver/lint')
Net::HTTP.start(uri.host, uri.port) do |http|
  # lint the active expectations
  puts http.request(Net::HTTP::Put.new(uri)).body

  # lint expectations without storing them
  req = Net::HTTP::Put.new(uri, 'Content-Type' => 'application/json')
  req.body = JSON.generate([
    { httpRequest: { method: 'GET', path: '/users/.*' }, httpResponse: { statusCode: 200 } },
    { httpRequest: { method: 'GET', path: '/users/1' }, httpResponse: { statusCode: 404 } }
  ])
  puts http.request(req).body
end
import (
    "log"
    "net/http"
    "strings"
)

// no typed method - call the REST endpoint
// lint the active expectations
req, _ := http.NewRequest("PUT", "http://localhost:1080/mockserver/lint", nil)
resp, err := http.DefaultClient.Do(req)
if err != nil {
    log.Fatal(err)
}
resp.Body.Close()

// lint expectations without storing them
expectations := `[
    {"httpRequest": {"method": "GET", "path": "/users/.*"}, "httpResponse": {"statusCode": 200}},
    {"httpRequest": {"method": "GET", "path": "/users/1"}, "httpResponse": {"statusCode": 404}}
]`
req, _ = http.NewRequest("PUT", "http://localhost:1080/mockserver/lint", strings.NewReader(expectations))
req.Header.Set("Content-Type", "application/json")
resp, err = http.DefaultClient.Do(req)
if err != nil {
    log.Fatal(err)
}
defer resp.Body.Close()
using System.Net.Http;
using System.Text;

// no typed method - call the REST endpoint
using var http = new HttpClient();

// lint the active expectations
var response = await http.PutAsync("http://localhost:1080/mockserver/lint", null);
Console.WriteLine(await response.Content.ReadAsStringAsync());

// lint expectations without storing them
var expectations = new StringContent("""
    [
        { "httpRequest": { "method": "GET", "path": "/users/.*" }, "httpResponse": { "statusCode": 200 } },
        { "httpRequest": { "method": "GET", "path": "/users/1" }, "httpResponse": { "statusCode": 404 } }
    ]
    """, Encoding.UTF8, "application/json");
response = await http.PutAsync("http://localhost:1080/mockserver/lint", expectations);
Console.WriteLine(await response.Content.ReadAsStringAsync());
use reqwest::blocking::Client;

// no typed method - call the REST endpoint
let http = Client::new();

// lint the active expectations
let report = http.put("http://localhost:1080/mockserver/lint").send().unwrap().text().unwrap();
println!("{}", report);

// lint expectations without storing them
let report = http
    .put("http://localhost:1080/mockserver/lint")
    .header("Content-Type", "application/json")
    .body(r#"[
        {"httpRequest": {"method": "GET", "path": "/users/.*"}, "httpResponse": {"statusCode": 200}},
        {"httpRequest": {"method": "GET", "path": "/users/1"}, "httpResponse": {"statusCode": 404}}
    ]"#)
    .send()
    .unwrap()
    .text()
    .unwrap();
println!("{}", report);
use GuzzleHttp\Client;

// no typed method - call the REST endpoint
$http = new Client();

// lint the active expectations
$response = $http->put('http://localhost:1080/mockserver/lint');
echo (string) $response->getBody();

// lint expectations without storing them
$response = $http->put('http://localhost:1080/mockserver/lint', [
    'json' => [
        ['httpRequest' => ['method' => 'GET', 'path' => '/users/.*'], 'httpResponse' => ['statusCode' => 200]],
        ['httpRequest' => ['method' => 'GET', 'path' => '/users/1'], 'httpResponse' => ['statusCode' => 404]],
    ],
]);
echo (string) $response->getBody();
# lint the active expectations
curl -X PUT "http://localhost:1080/mockserver/lint"

# lint expectations without storing them
curl -X PUT "http://localhost:1080/mockserver/lint" -d '[
    { "httpRequest": { "method": "GET", "path": "/users/.*" }, "httpResponse": { "statusCode": 200 } },
    { "httpRequest": { "method": "GET", "path": "/users/1" }, "httpResponse": { "statusCode": 404 } }
]'

For the second curl above, the second expectation can never be used, because the first one is evaluated first and matches every GET /users/1:

{
  "source": "request",
  "expectationsChecked": 2,
  "issueCount": 1,
  "errorCount": 0,
  "warningCount": 1,
  "infoCount": 0,
  "shadowCheckTruncated": false,
  "issues": [
    {
      "rule": "SHADOWED",
      "severity": "WARNING",
      "index": 1,
      "summary": "GET /users/1",
      "message": "never matches: GET /users/.* is evaluated first (same priority, created earlier), matches every request this one matches, and never runs out (...)",
      "relatedIndex": 0,
      "relatedSummary": "GET /users/.*"
    }
  ]
}

Fields are shortened here. With an empty body, issues name the stored expectation's expectationId and its position in the order MockServer evaluates expectations (from 1); with expectations in the body, index is the entry's place in the array (from 0).

 

Typed Log Entry Retrieval

Instead of retrieving logs as plain text, you can retrieve structured LogEntry objects. This is useful for programmatic analysis — filtering by type, inspecting expectations, or building custom reports:

Each entry writes its request and response in full once, in httpRequest and httpResponse. Where the message and arguments refer to that same request or response, they show a short form instead: the method and path ("POST /orders") or the status code ("201"), so a large body is not repeated. For the same reason the expectation recorded for a proxied request is written without its request and response bodies. Read bodies from httpRequest and httpResponse; for the full rendered text of each message, retrieve the logs as plain text (format=JSON, the default). Before 9.0.0 the message and arguments repeated the full request and response.

LogEntry[] entries = mockServerClient.retrieveLogEntries(null);

LogEntry[] matchEntries = mockServerClient.retrieveLogEntries(
    request().withPath("/api/users")
);

// Deserialized entries include: type, logLevel, correlationId, epochTime,
// timestamp, port, expectationId, messageFormat, arguments, because.
// An argument that is the entry's own request or response arrives as a
// short string such as "POST /orders" or "201".
// Note: httpRequest, httpResponse, expectation, throwable are in the JSON
// but not deserialized (returned as null on the client).
for (LogEntry entry : entries) {
    System.out.println(entry.getType() + " at " + entry.getTimestamp());
}
// structured LOG_ENTRIES have no typed method - call the REST endpoint.
// Send a request-matcher JSON body to filter (omit for all entries).
const response = await fetch(
  "http://localhost:1080/mockserver/retrieve?type=LOGS&format=LOG_ENTRIES",
  { method: "PUT" }
);
const entries = await response.json();
entries.forEach(function (entry) {
  console.log(entry.type + " at " + entry.timestamp);
});
import requests

# structured LOG_ENTRIES have no typed method - call the REST endpoint
response = requests.put(
    "http://localhost:1080/mockserver/retrieve",
    params={"type": "LOGS", "format": "LOG_ENTRIES"},
)
for entry in response.json():
    print(entry["type"], "at", entry["timestamp"])
require 'net/http'
require 'json'

# structured LOG_ENTRIES have no typed method - call the REST endpoint
uri = URI('http://localhost:1080/mockserver/retrieve?type=LOGS&format=LOG_ENTRIES')
response = Net::HTTP.start(uri.host, uri.port) { |http| http.request(Net::HTTP::Put.new(uri)) }
JSON.parse(response.body).each do |entry|
  puts "#{entry['type']} at #{entry['timestamp']}"
end
import (
    "encoding/json"
    "fmt"
    "net/http"
)

// structured LOG_ENTRIES have no typed method - call the REST endpoint
req, _ := http.NewRequest("PUT", "http://localhost:1080/mockserver/retrieve?type=LOGS&format=LOG_ENTRIES", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
var entries []map[string]interface{}
json.NewDecoder(resp.Body).Decode(&entries)
for _, entry := range entries {
    fmt.Printf("%v at %v\n", entry["type"], entry["timestamp"])
}
using System.Net.Http;

// structured LOG_ENTRIES have no typed method - call the REST endpoint
using var http = new HttpClient();
var response = await http.PutAsync(
    "http://localhost:1080/mockserver/retrieve?type=LOGS&format=LOG_ENTRIES", null);
var entriesJson = await response.Content.ReadAsStringAsync();
use reqwest::blocking::Client;
use serde_json::Value;

// structured LOG_ENTRIES have no typed method - call the REST endpoint
let entries: Vec<Value> = Client::new()
    .put("http://localhost:1080/mockserver/retrieve?type=LOGS&format=LOG_ENTRIES")
    .send()
    .unwrap()
    .json()
    .unwrap();
for entry in &entries {
    println!("{} at {}", entry["type"], entry["timestamp"]);
}
use GuzzleHttp\Client;

// structured LOG_ENTRIES have no typed method - call the REST endpoint
$http = new Client();
$response = $http->put('http://localhost:1080/mockserver/retrieve', [
    'query' => ['type' => 'LOGS', 'format' => 'LOG_ENTRIES'],
]);
$entries = json_decode((string) $response->getBody(), true);
# use format=LOG_ENTRIES to get structured JSON log entries
curl -X PUT "http://localhost:1080/mockserver/retrieve?type=LOGS&format=LOG_ENTRIES"
LogEntry[] entries = mockServerClient.retrieveLogEntriesByCorrelationId("abc-123-def-456");
// no typed method - call the REST endpoint with correlationId
const response = await fetch(
  "http://localhost:1080/mockserver/retrieve?type=LOGS&format=LOG_ENTRIES&correlationId=abc-123-def-456",
  { method: "PUT" }
);
const entries = await response.json();
import requests

# no typed method - call the REST endpoint with correlationId
response = requests.put(
    "http://localhost:1080/mockserver/retrieve",
    params={"type": "LOGS", "format": "LOG_ENTRIES", "correlationId": "abc-123-def-456"},
)
entries = response.json()
require 'net/http'
require 'json'

# no typed method - call the REST endpoint with correlationId
uri = URI('http://localhost:1080/mockserver/retrieve?type=LOGS&format=LOG_ENTRIES&correlationId=abc-123-def-456')
response = Net::HTTP.start(uri.host, uri.port) { |http| http.request(Net::HTTP::Put.new(uri)) }
entries = JSON.parse(response.body)
import (
    "encoding/json"
    "net/http"
)

// no typed method - call the REST endpoint with correlationId
url := "http://localhost:1080/mockserver/retrieve?type=LOGS&format=LOG_ENTRIES&correlationId=abc-123-def-456"
req, _ := http.NewRequest("PUT", url, nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
var entries []map[string]interface{}
json.NewDecoder(resp.Body).Decode(&entries)
using System.Net.Http;

// no typed method - call the REST endpoint with correlationId
using var http = new HttpClient();
var response = await http.PutAsync(
    "http://localhost:1080/mockserver/retrieve?type=LOGS&format=LOG_ENTRIES&correlationId=abc-123-def-456",
    null);
var entriesJson = await response.Content.ReadAsStringAsync();
use reqwest::blocking::Client;
use serde_json::Value;

// no typed method - call the REST endpoint with correlationId
let url = "http://localhost:1080/mockserver/retrieve?type=LOGS&format=LOG_ENTRIES&correlationId=abc-123-def-456";
let entries: Vec<Value> = Client::new().put(url).send().unwrap().json().unwrap();
use GuzzleHttp\Client;

// no typed method - call the REST endpoint with correlationId
$http = new Client();
$response = $http->put('http://localhost:1080/mockserver/retrieve', [
    'query' => [
        'type' => 'LOGS',
        'format' => 'LOG_ENTRIES',
        'correlationId' => 'abc-123-def-456',
    ],
]);
$entries = json_decode((string) $response->getBody(), true);
curl -X PUT "http://localhost:1080/mockserver/retrieve?type=LOGS&format=LOG_ENTRIES&correlationId=abc-123-def-456"
 

Time-Filtered Retrieval

When debugging intermittent issues, you can narrow log entries to a specific time window:

long now = System.currentTimeMillis();
LogEntry[] recentEntries = mockServerClient.retrieveLogEntries(
    null, now - 30_000, now
);
// time filtering is applied client-side on each entry's epochTime field
const now = Date.now();
const response = await fetch(
  "http://localhost:1080/mockserver/retrieve?type=LOGS&format=LOG_ENTRIES",
  { method: "PUT" }
);
const entries = await response.json();
const recentEntries = entries.filter(function (entry) {
  return entry.epochTime >= now - 30000 && entry.epochTime < now;
});
import time
import requests

# time filtering is applied client-side on each entry's epochTime field
now = int(time.time() * 1000)
response = requests.put(
    "http://localhost:1080/mockserver/retrieve",
    params={"type": "LOGS", "format": "LOG_ENTRIES"},
)
recent_entries = [
    entry for entry in response.json()
    if now - 30000 <= entry["epochTime"] < now
]
require 'net/http'
require 'json'

# time filtering is applied client-side on each entry's epochTime field
now = (Time.now.to_f * 1000).to_i
uri = URI('http://localhost:1080/mockserver/retrieve?type=LOGS&format=LOG_ENTRIES')
response = Net::HTTP.start(uri.host, uri.port) { |http| http.request(Net::HTTP::Put.new(uri)) }
recent_entries = JSON.parse(response.body).select do |entry|
  entry['epochTime'] >= now - 30_000 && entry['epochTime'] < now
end
import (
    "encoding/json"
    "net/http"
    "time"
)

// time filtering is applied client-side on each entry's epochTime field
now := time.Now().UnixMilli()
req, _ := http.NewRequest("PUT", "http://localhost:1080/mockserver/retrieve?type=LOGS&format=LOG_ENTRIES", nil)
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
var entries []map[string]interface{}
json.NewDecoder(resp.Body).Decode(&entries)
var recentEntries []map[string]interface{}
for _, entry := range entries {
    epochTime := int64(entry["epochTime"].(float64))
    if epochTime >= now-30000 && epochTime < now {
        recentEntries = append(recentEntries, entry)
    }
}
using System.Linq;
using System.Net.Http;
using System.Text.Json;

// time filtering is applied client-side on each entry's epochTime field
var now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
using var http = new HttpClient();
var response = await http.PutAsync(
    "http://localhost:1080/mockserver/retrieve?type=LOGS&format=LOG_ENTRIES", null);
var entries = JsonDocument.Parse(await response.Content.ReadAsStringAsync()).RootElement;
var recentEntries = entries.EnumerateArray()
    .Where(entry => {
        var epochTime = entry.GetProperty("epochTime").GetInt64();
        return epochTime >= now - 30000 && epochTime < now;
    })
    .ToList();
use reqwest::blocking::Client;
use serde_json::Value;
use std::time::{SystemTime, UNIX_EPOCH};

// time filtering is applied client-side on each entry's epochTime field
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as i64;
let entries: Vec<Value> = Client::new()
    .put("http://localhost:1080/mockserver/retrieve?type=LOGS&format=LOG_ENTRIES")
    .send()
    .unwrap()
    .json()
    .unwrap();
let recent_entries: Vec<&Value> = entries
    .iter()
    .filter(|entry| {
        let epoch_time = entry["epochTime"].as_i64().unwrap_or(0);
        epoch_time >= now - 30_000 && epoch_time < now
    })
    .collect();
use GuzzleHttp\Client;

// time filtering is applied client-side on each entry's epochTime field
$now = (int) (microtime(true) * 1000);
$http = new Client();
$response = $http->put('http://localhost:1080/mockserver/retrieve', [
    'query' => ['type' => 'LOGS', 'format' => 'LOG_ENTRIES'],
]);
$entries = json_decode((string) $response->getBody(), true);
$recentEntries = array_filter($entries, function ($entry) use ($now) {
    return $entry['epochTime'] >= $now - 30000 && $entry['epochTime'] < $now;
});
# the retrieve endpoint returns all log entries; the time window is applied
# client-side by filtering each entry on its epochTime field
curl -X PUT "http://localhost:1080/mockserver/retrieve?type=LOGS&format=LOG_ENTRIES"
 

Related Pages

  • Creating Expectations — the full reference for request matchers and response actions, so you can correct the expectation that is not matching
  • Verify Requests — confirm which requests actually reached MockServer while diagnosing a mismatch
  • Clearing & Resetting — rule out stale expectations or recorded requests left over from an earlier test