skip to content

Your WireMock water-rota stub returns valid JSON but the client refuses to parse it — why?

level: seniorimportance: must knowfreq 55%

answer

  1. the body was never the broken part
  2. a reply is status, headers and body
  3. the stub declared no media type
  4. clients dispatch before they decode
  5. add withHeader Content-Type application/json

basics

~20 s

Almost always the stub set a body and no media type. In WireMock the reply carries only the headers the response definition declares, so a client that checks Content-Type before decoding rejects a perfectly correct body. Add withHeader explicitly.

solid answer

~50 s

In WireMock a response definition is entirely explicit: `aResponse().withStatus(200).withBody("{...}")` sets a status and body bytes and declares no media type, because WireMock sends the headers the stub defined and nothing else. Many clients dispatch on `Content-Type` before they decode — a message converter picks a reader by media type, a deserialiser refuses an unannounced payload — so the body being valid JSON is beside the point. The fix is one call: WireMock's `withHeader("Content-Type", "application/json")` alongside the body. The diagnosis generalises: when a stubbed reply is right and the client still fails, compare the *whole* reply with the real service's, headers included, not just the payload. It is the same class of bug as a missing `Location` on a created resource or a missing cache header — the body was never the part that was wrong.

code

java · 5 lines
java
stubFor(get(urlPathEqualTo("/allotment/rota/weeks/2026-W24"))
    .willReturn(aResponse()
        .withStatus(200)
        .withHeader("Content-Type", "application/json")
        .withBody("{ \"rotaWeek\": \"2026-W24\", \"hosepipeBan\": false }")));

go deeper

for a junior

Remember that a WireMock reply has three parts and headers are one of them. If a client cannot read your JSON, check whether the stub set Content-Type before you touch the payload.

for a middle

Explain why it fails: WireMock sends the headers the stub defined, and clients often select a reader by media type before decoding, so a valid body announced as nothing is unusable.

for a senior

Demonstrate the diagnosis order — did it match, what status, what headers, then the body — and generalise the lesson to any reply field the client reads as control information.

for a principal

Own the convention that prevents it: a shared way to build stubbed replies so the media type is never optional, and a review expectation that a fake models the whole response, not the payload.

## The symptom A stub for the allotment water-rota API answers `GET /allotment/rota/weeks/2026-W24` with a body you can paste into a JSON validator and a status of 200, and the client under test fails anyway — a deserialisation error, an empty model object, a message converter complaining it cannot read the response. The body is correct. The test is red. This is one of the most common half-hours lost to a stub server, and the cause is nearly always the same. ## Why it happens In WireMock, a response definition is a set of fields you filled in. WireMock's `withStatus(` sets the code, WireMock's `withBody(` sets the bytes, and WireMock's `withHeader(` sets headers — one call each. A stub that never calls `withHeader(` declares no media type, so the reply is bytes with no statement about what they are. The real service almost certainly does declare one, and the client was written against the real service. So the mismatch is not in the payload but in everything around it: - A client that selects a reader by media type has no reader to select and fails before parsing. - A client that asserts on the response's content type fails its own precondition. - A client that guesses may guess text and hand you a string where an object was expected. None of these are WireMock misbehaving. WireMock served exactly the reply that was defined; the definition was incomplete. ## The fix, and the habit behind it The fix is one line — WireMock's `withHeader("Content-Type", "application/json")` beside the body — but the habit is what stops it recurring: 1. **Model the whole reply, not the payload.** A canned reply is a status, a set of headers and a body. Two of those three are routinely forgotten. 2. **Copy the real response's headers when you build the stub.** If the live water-rota service answers with a particular media type, the stub says the same thing. 3. **Make it a convention.** Every stub in a suite that returns JSON sets the media type, so no reviewer has to notice its absence. 4. **Assert the failure once.** A single test that pins the client's behaviour against a stub with the media type set is cheap insurance for the rest. ## Where the same gap bites elsewhere The missing-header class is wider than JSON: - A binary reply served with WireMock's `withBase64Body(` and no media type — the bytes are perfect and the client still cannot dispatch on them. - A file-backed body served with WireMock's `withBodyFile(` — moving the payload to a file does not move the headers with it, and they are still the mapping's job. - A stub reproducing a created-resource response whose `Location` header the client follows. - Any header the client reads as control information rather than data — the stub's body being right proves nothing about those. In each case the diagnostic move is identical: put the stubbed reply and the real reply side by side, whole, and look at what differs above the body. ## A diagnosis order that works When a WireMock-backed test fails and the body looks right, walk it in this order: - **Did the stub even match?** A miss is a different failure and answers a default, not your body. Rule it out before reading the payload. - **What status did the client actually see?** WireMock's builder answers 200 when `withStatus(` was never called, which can disguise a stub you meant to be an error. - **What headers did the reply carry?** This is the step that catches the present bug. - **Only then, the payload.** By this point the body is usually innocent. The order matters because the body is the part you wrote most recently and therefore the part you suspect first, and it is the least likely culprit. ## What interviewers listen for - That you diagnose the *reply as a whole* rather than staring at the payload, and say so before naming the fix. - That you can state the mechanism plainly: WireMock sends the headers the stub defines, and the body call defines none. - That you generalise it — this is a modelling gap in the fake, not a quirk of one product, and the same gap appears in any stub server whose reply fields are set independently. - That you turn the fix into a convention rather than a one-off patch, because the next person will make the same omission. The shortest form of the whole answer: in WireMock a stub returns the reply you described, and a description with no `withHeader(` call describes no media type. The payload was never the part that was wrong.

  • How would you stop this recurring across a suite of WireMock stubs?
    Make the reply a template your team writes the same way every time: a small helper that takes a status and a body and always sets the media type, or a review convention that a JSON stub without `withHeader("Content-Type", ...)` is incomplete. The point is to remove the chance to forget rather than to remember harder.
  • Does moving the body into withBodyFile() carry the headers with it?
    No. In WireMock the headers stay in the response definition; only the body's source changed. A stub that served the right media type inline and then moved its payload to a file still declares that header in the mapping, and a stub that never declared one still does not.
  • What else besides Content-Type is worth copying from the real service into a stub?
    Anything the client treats as control information rather than data: a `Location` on a created resource the client follows, caching or correlation headers it reads, and the status the real endpoint actually returns. The test only proves the client works if the fake resembles the real reply in the parts the client inspects.

A parcel with exactly the right contents and no label on the outside. The sorting machine goes by the label, so a perfect parcel gets rejected without ever being opened.

saying these in an interview costs you the question

  • Blaming the client's parser for a stub's missing header
  • Assuming WireMock infers a media type from the body
  • Rewriting the JSON payload instead of reading the headers
  • Thinking withBodyFile carries headers along with the body
  • Treating a stubbed reply as only a status and a body