skip to content

What is the difference between strict and lenient mode in `content().json(...)`, and when would you use each?

level: middleimportance: should knowfreq 60%

answer

  1. json() = semantic compare via JSONassert
  2. lenient = extra fields OK + order-agnostic arrays
  3. strict = exact, no extras, ordered arrays
  4. 6.2: JsonCompareMode enum replaces boolean
  5. lenient default = resilient to additive change

basics

~10 s

content().json(expected) compares the whole body as JSON. Lenient (default) allows extra fields and ignores array order; strict requires an exact match — same fields, no extras, and same array order.

solid answer

~40 s

`content().json(String)` compares the response body against expected JSON semantically (via JSONassert), not as raw text, so whitespace and key order don't matter. The default is **lenient**: the actual body may contain extra fields the expected doesn't mention, and array element order is not enforced — it checks that everything you asserted is present. **Strict** mode (`json(expected, true)` in older Spring, or `json(expected, JsonCompareMode.STRICT)` in Spring 6.2+) requires an exact structural match: no unexpected extra fields and array ordering must match. Use lenient when you only care about a subset of a large payload and want tests resilient to additive changes; use strict for contract tests where extra or reordered data is a defect. Lenient is the safer default for most controller tests to avoid brittle failures on unrelated additions.

code

java · 15 lines
java
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;

// Lenient (default): passes even if body has extra fields
mockMvc.perform(get("/users/1"))
    .andExpect(content().json("{\"name\":\"Ada\",\"age\":36}"));

// Strict (Spring 6.2+): exact structure, ordered arrays
mockMvc.perform(get("/users/1"))
    .andExpect(content().json(
        "{\"name\":\"Ada\",\"age\":36,\"email\":\"[email protected]\"}",
        JsonCompareMode.STRICT));

// Legacy boolean overload (deprecated in 6.2): true = strict
mockMvc.perform(get("/users/1"))
    .andExpect(content().json("{...}", true));

go deeper

for a junior

Knows content().json() compares JSON semantically, ignores whitespace.

for a middle

Articulates lenient=extensible+unordered-arrays vs strict=exact, and picks appropriately.

for a senior

Knows the 6.2 JsonCompareMode migration and the leaked-field/strict-mode contract-test angle.

for a principal

Sets team policy: lenient for internal slice tests, strict for published API contracts; documents the array-ordering trap.

**What `content().json(...)` does:** `content()` returns a `ContentResultMatchers`; its `.json(String expectedJson)` overload compares the response body against the expected JSON **semantically**. Internally Spring delegates to the **JSONassert** library, which parses both sides into JSON trees and compares them — so key ordering and whitespace/formatting are irrelevant (`{"a":1,"b":2}` equals `{ "b": 2, "a": 1 }`). **The two modes:** - **Lenient (default):** corresponds to JSONassert's `LENIENT` mode. Two relaxations: (1) **extensible** — the actual body may have extra fields not present in the expected JSON; (2) **non-strict array order** — arrays are compared as sets, order-insensitive. It asserts "everything I specified is there and correct," ignoring the rest. - **Strict:** corresponds to `STRICT` mode. The actual and expected must match exactly — no extra fields allowed, and array element order must be identical. **How to select the mode:** - Old boolean overload: `content().json(expected, true)` = strict, `false`/no-arg = lenient. - Spring Framework 6.2+ added `content().json(expected, JsonCompareMode.STRICT)` / `JsonCompareMode.LENIENT`, and the boolean overload is deprecated in favor of the enum for readability. **Choosing a mode:** - **Lenient** for typical controller slice tests: your endpoint may legitimately grow new fields; a lenient assertion on the fields you care about won't break every time someone adds a property. - **Strict** for contract/consumer tests where the exact shape is the guarantee — an unexpected extra field or reordered array is a real regression (e.g. leaking an internal field, or an ordering-sensitive API). **Gotchas:** - Strict array ordering surprises people: `[1,2,3]` vs `[3,2,1]` passes lenient, fails strict. If order is semantically irrelevant but you still want strict-on-fields, JSONassert also has `NON_EXTENSIBLE` and `STRICT_ORDER` modes, though Spring's convenience overloads expose only lenient/strict. - `content().json(...)` needs a well-formed JSON body; a truncated/HTML error body yields a parse failure. - Number comparison in JSONassert is value-based (`1` vs `1.0` are treated as equal numerically), which is more forgiving than `jsonPath(...).value()` type checks. - For asserting a single field, prefer `jsonPath`; `content().json` shines for whole-object or whole-array equality.

  • Does `content().json(...)` care about the order of object keys?
    No. Even in strict mode, object key order is irrelevant — JSONassert compares objects by key, not position. Strict mode only enforces no-extra-fields and array element ordering, not object key ordering.
  • Your test using lenient mode passes but the endpoint accidentally leaks an internal `passwordHash` field. Why didn't the test catch it, and how would you?
    Lenient mode allows extra fields, so the leaked field is ignored. Switch to strict mode (JsonCompareMode.STRICT) so any unexpected field fails the assertion, or add an explicit `jsonPath("$.passwordHash").doesNotExist()`.

saying these in an interview costs you the question

  • Claiming `content().json()` is a raw string/text comparison sensitive to whitespace
  • Thinking strict mode enforces object key ordering
  • Believing lenient mode fails when the response has extra fields

context