skip to content

JSONPath & Content Assertions

JSONPath expressions and strict or lenient JSON comparison let you assert on a response body without string matching. Interviewers ask about strict comparison, because over-specified assertions break on every harmless field addition.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

questions

5

In a MockMvc test, how do you assert that a JSON response body has a field `name` equal to "Ada"?

level: juniorimportance: must knowfreq 80%

answer

  1. $ = root, dot navigates
  2. jsonPath from MockMvcResultMatchers
  3. returns JsonPathResultMatchers
  4. .value() / .exists() / .isArray()
  5. chain inside andExpect

basics

~10 s

Use .andExpect(jsonPath("$.name").value("Ada")), statically importing jsonPath from MockMvcResultMatchers. $ is the JSON root and .name navigates to the field.

solid answer

~30 s

After performing the request you chain `.andExpect(jsonPath("$.name").value("Ada"))`. `jsonPath` is a static method on `org.springframework.test.web.servlet.result.MockMvcResultMatchers` that returns a `JsonPathResultMatchers`; `$` denotes the JSON document root and `$.name` is the JSONPath expression selecting the top-level `name` property. `.value(...)` asserts the extracted value equals the expected object. You can layer more expectations on the same `andExpect` chain — `jsonPath("$.id").exists()`, `jsonPath("$.age").value(36)`, etc. This only works when the response `Content-Type` is JSON so the path evaluator can parse the body. It's the standard, readable way to assert on individual fields without deserializing the whole payload into a DTO.

code

java · 11 lines
java
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

@Test
void returnsUser() throws Exception {
    mockMvc.perform(get("/users/1"))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$.name").value("Ada"))
        .andExpect(jsonPath("$.age").value(36))
        .andExpect(jsonPath("$.email").exists());
}

go deeper

for a junior

Know $ is root, jsonPath("$.field").value(...) is the core assertion, and it lives on MockMvcResultMatchers.

for a middle

Should also reach for .exists(), .isArray(), and Hamcrest matcher overloads.

for a senior

Explains number-typing gotcha and null-vs-absent distinction without prompting.

for a principal

Frames jsonPath as field-level assertion vs whole-document compare and sets team conventions for readability.

**What JSONPath is:** JSONPath is a query language for JSON, analogous to XPath for XML. An expression starts at the document **root**, written `$`. `$.name` selects the `name` property of the root object; `$.address.city` navigates nested objects; `$.items[0]` indexes into an array; `$.items[*].id` selects the `id` of every array element. Spring uses the **Jayway JsonPath** library under the hood to evaluate these expressions against the response body. **Where the matcher comes from:** In MockMvc you statically import `jsonPath` from `org.springframework.test.web.servlet.result.MockMvcResultMatchers` (or its aggregator `MockMvcResultMatchers.jsonPath(...)`). It returns a `JsonPathResultMatchers` object exposing assertion methods: - `.value(Object)` — the selected value equals the expected value. - `.value(Matcher<T>)` — the value satisfies a Hamcrest matcher (e.g. `greaterThan(10)`). - `.exists()` / `.doesNotExist()` — the path resolves / does not resolve. - `.isEmpty()` / `.isNotEmpty()` — the value is empty/absent vs present and non-empty. - `.isArray()`, `.isMap()`, `.isString()`, `.isNumber()`, `.isBoolean()` — type assertions. **How it plugs in:** `mockMvc.perform(get("/users/1")).andExpect(status().isOk()).andExpect(jsonPath("$.name").value("Ada"))`. Each `andExpect` takes a `ResultMatcher`; `jsonPath(...).value(...)` produces one. Assertions are evaluated against the recorded `MockHttpServletResponse` body. **Gotchas:** - The response must actually be JSON; if the endpoint returns an error HTML page or empty body the path evaluation throws and the test fails with a parse error, not a clean mismatch. - Number typing: JsonPath deserializes small integers to `Integer`. `.value(36)` works; `.value(36L)` fails because `Long` ≠ `Integer`. Prefer Hamcrest `is(36)` or match the exact type. - `$` is required — a bare `name` is not a valid root-relative expression here. - `.value(null)` asserts the field is JSON `null` (present but null), which differs from `.doesNotExist()` (absent key). **When to use:** Use `jsonPath` for targeted, field-level assertions that stay readable and don't require you to deserialize the whole body. For full-document equality, prefer `content().json(...)` instead.

  • What is the difference between `jsonPath("$.email").doesNotExist()` and `jsonPath("$.email").isEmpty()`?
    `doesNotExist()` passes when the key is absent from the JSON. `isEmpty()` passes when the key is present but its value is null or an empty string/array/collection. A present-but-null field satisfies `isEmpty()` but fails `doesNotExist()`.
  • Why might `jsonPath("$.id").value(1L)` fail even though the response id is 1?
    JsonPath deserializes the small integer to `Integer`, and `.value(1L)` compares against a `Long`, so equality fails on type. Use `.value(1)` or `jsonPath("$.id").value(is(1))`.

saying these in an interview costs you the question

  • Thinking you must deserialize the whole body into a DTO to assert one field
  • Writing the path without the `$` root
  • Believing `.value(null)` and `.doesNotExist()` mean the same thing

context

open as a page

How do `content().string(...)` and the Hamcrest matcher overload of `jsonPath(...)` let you write richer assertions?

level: middleimportance: should knowfreq 45%

basics

~10 s

content().string(Matcher) runs a Hamcrest matcher against the raw body string (e.g. containsString("ok")). jsonPath("$.list").value(hasSize(3)) passes a Hamcrest matcher against a selected JSON value for flexible, non-equality checks.

open as a page

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

level: middleimportance: should knowfreq 60%

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.

open as a page

How do JSONPath assertions differ between MockMvc and WebTestClient?

level: seniorimportance: should knowfreq 40%

basics

~10 s

The JSONPath expression syntax is identical, but the assertion APIs differ. MockMvc uses jsonPath("$.x").value(...) (JsonPathResultMatchers) inside andExpect. WebTestClient uses .expectBody().jsonPath("$.x").isEqualTo(...) (JsonPathAssertions), a fluent chain with no static import.

open as a page

What are the subtle traps with JSONPath number typing, and the difference between `exists`/`doesNotExist`/`isEmpty`/`isNotEmpty`?

level: principalimportance: nice to knowfreq 30%

basics

~20 s

JsonPath maps small JSON integers to Integer and decimals to Double, so .value(1L) fails against 1. exists/doesNotExist test key presence; isEmpty/isNotEmpty test whether a present value is null/empty vs populated. A present-but-null field exists AND isEmpty.

open as a page