Troubleshooting Matching
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:
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);
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);
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"
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 8/12 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 (in the request but expected in the matcher) 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. |
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.
REST API
curl -X PUT "http://localhost:1080/mockserver/debugMismatch" \
-H "Content-Type: application/json" \
-d '{
"method": "GET",
"path": "/api/users",
"headers": {
"Content-Type": ["application/json"]
}
}'
Java Client
String result = mockServerClient.debugMismatch(
request()
.withMethod("GET")
.withPath("/api/users")
.withHeader("Content-Type", "application/json")
);
System.out.println(result);
Response Format
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"
]
}
}
]
}
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: 11/12 (all fields matched except method)
This endpoint is also available via the MCP debug_request_mismatch tool for AI-assisted debugging.
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
correlationIdfield when usingformat=LOG_ENTRIES
Retrieving by Correlation ID
Once 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.
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:
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"
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
);