--- 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.

## 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 compared
- `closestMatch` — which expectation had the fewest differences
- `results` — 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 `correlationId` field when using `format=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
    );