--- title: Troubleshooting Matching description: Step-by-step checklist to diagnose why MockServer returns 404 when a request does not match, including common pitfalls and the debug mismatch endpoint. shortTitle: Troubleshooting layout: page pageOrder: 9 section: 'Mock Server' subsection: true sitemap: priority: 0.8 changefreq: 'monthly' lastmod: 2025-05-10T08:00:00+01:00 ---
When an expectation does not match a request, MockServer returns a 404 response. This page explains how to diagnose and fix matching issues.
Follow these steps in order when a request is not matching:
Expectations expire after their configured times limit or timeToLive. Retrieve active expectations to confirm yours is still present:
Expectation[] active = mockServerClient.retrieveActiveExpectations(null);
curl -X PUT "http://localhost:1080/mockserver/retrieve?type=ACTIVE_EXPECTATIONS"
Verify that MockServer received the request:
HttpRequest[] requests = mockServerClient.retrieveRecordedRequests(null);
curl -X PUT "http://localhost:1080/mockserver/retrieve?type=REQUESTS"
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"
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).
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.
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 8/12 fields"). This immediately tells you which expectation to investigate.
These are the most frequent reasons expectations fail to match:
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.
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:
.withHeader("Content-Type", "application/json; charset=utf-8")MockServer will show a HINT when it detects this pattern.
By default, JSON body matching uses ONLY_MATCHING_FIELDS mode:
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))
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.
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 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")
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.
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.
| 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. |
For full configuration details, see the Configuration page.
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.
curl -X PUT "http://localhost:1080/mockserver/debugMismatch" \
-H "Content-Type: application/json" \
-d '{
"method": "GET",
"path": "/api/users",
"headers": {
"Content-Type": ["application/json"]
}
}'
String result = mockServerClient.debugMismatch(
request()
.withMethod("GET")
.withPath("/api/users")
.withHeader("Content-Type", "application/json")
);
System.out.println(result);
The response is a JSON object with:
totalExpectations — the number of active expectations comparedclosestMatch — which expectation had the fewest differencesresults — per-expectation match analysis with field-level differences{
"totalExpectations": 2,
"closestMatch": {
"expectationId": "abc-123",
"matchedFields": 10,
"totalFields": 12
},
"results": [
{
"expectationId": "abc-123",
"expectationPath": "/api/users",
"expectationMethod": "POST",
"matches": false,
"matchedFieldCount": 10,
"totalFieldCount": 12,
"differences": {
"method": [
"string or regex match failed expected: POST found: GET"
]
}
}
]
}
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: 11/12 (all fields matched except method)
This endpoint is also available via the MCP debug_request_mismatch tool for AI-assisted debugging.
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.
The correlation ID appears in:
correlationId field when using format=LOG_ENTRIESOnce you have a correlation ID, retrieve all log entries for that request:
curl -X PUT "http://localhost:1080/mockserver/retrieve?type=LOGS&correlationId=abc-123-def-456"
String logs = mockServerClient.retrieveLogsByCorrelationId("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.
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:
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.
// 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());
}
LogEntry[] entries = mockServerClient.retrieveLogEntriesByCorrelationId("abc-123-def-456");
# REST API - use format=LOG_ENTRIES to get structured JSON
curl -X PUT "http://localhost:1080/mockserver/retrieve?type=LOGS&format=LOG_ENTRIES"
# Combined with correlation ID filter
curl -X PUT "http://localhost:1080/mockserver/retrieve?type=LOGS&format=LOG_ENTRIES&correlationId=abc-123-def-456"
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
);