In REST Assured, why is calling extract().path() six times on one response worse than holding a JsonPath?
answer
- convenience wrapper, not a cached view
- one new path object per call
- content type picks the engine
- hold the JsonPath, query it repeatedly
- typed getters live on JsonPath
basics
~20 sEach extract().path(...) call builds a fresh path object over the body, so six calls parse the same document six times. Hold one extract().jsonPath() instead: it caches its parse after the first read and takes a JsonPathConfig.
solid answer
~40 s`extract().path(...)` is a convenience wrapper. It picks the engine from the response content type — `json` gives a `JsonPath`, `xml` an `XmlPath`, `html` the HTML mode, and no content type with no `RestAssured.defaultParser` raises `IllegalStateException` — and it builds a **new** path object for each call, so reading six fields parses the body six times. `extract().jsonPath()` hands you one `JsonPath` that memoises its parsed document after the first query, so the same six reads parse once, and it gives you typed accessors: `getString`, `getInt`, `getList`, `getMap`, `getObject`. Note that `getList` is not on the extract side — the chain is `extract().jsonPath().getList("gainCurve")`. Use `path(...)` for a single field, a held path object for several or for typed reads, and `xmlPath()`/`htmlPath()` when you want to pin the engine rather than let the response's `Content-Type` choose it.
code
java · 18 linesimport io.restassured.path.json.JsonPath;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;
JsonPath fitting =
given()
.when()
.get("/fittings/{id}", fittingId)
.then()
.statusCode(200)
.body("status", equalTo("VERIFIED"))
.extract()
.jsonPath();
String serial = fitting.getString("device.serial");
int bands = fitting.getList("gainCurve").size();
long verifiedAt = fitting.getLong("verifiedAt");go deeper
Know that extract().path("device.serial") reads one field and that extract().jsonPath() gives you an object you can query repeatedly. Being able to pick between them for one field versus several is enough here.
Explain the mechanics: path() resolves an engine from the content type and builds a new path object per call, while a held JsonPath parses once and offers typed getters such as getInt and getList.
Show the production angle - repeated path() calls inside loops or shared helpers, the IllegalStateException when no content type is present, and when you pin the engine with xmlPath() instead of letting a header decide.
Own it as a convention. Decide what your helpers return - a raw value, a held path object or the whole Response - and make the choice consistent so nobody re-parses a large payload field by field by accident.
## Two doors into the same body After `extract()` you have two ways to read a field out of the response body, and they are not equivalent. `path(String path, String... arguments)` is the one-liner: you hand it a path and it gives you back the value. `jsonPath()` — with its siblings `xmlPath()` and `htmlPath()` — hands you a *path object* that you keep and query as many times as you like. Choosing between them is a real decision, and the cost of getting it wrong grows with the number of fields you read. ## What `path()` does on every call `extract().path(...)` is a convenience wrapper with two jobs. First it resolves which path engine to use by looking at the response's own content type: - a content type containing `json` is read with a `JsonPath` - a content type containing `xml` is read with an `XmlPath` - a content type containing `html` is read in the HTML compatibility mode - no content type at all, and no `RestAssured.defaultParser` set, raises `IllegalStateException` - a content type that maps to none of those also raises `IllegalStateException` Second — and this is the part that matters here — it builds a **new** path object over the response body for that one lookup. Read six fields with six `path(...)` calls and REST Assured turns the body into a string and parses that document six times. For one small fitting record nobody notices; for a `GET /fittings/{id}` response carrying a full gain curve and a session history, inside a loop, in a suite of several hundred cases, it is measurable waste. ## What holding a path object changes `extract().jsonPath()` returns a `JsonPath` built over the same body. That object memoises its parsed document: the first query parses, and every query after that reads the structure already in memory. So the shape you want for multi-field reads is one extraction, one path object, many reads. | | `extract().path("...")` | `extract().jsonPath()` held in a variable | |---|---|---| | Engine chosen by | the response content type | you, explicitly | | Parses per field read | once per call | once in total | | Typed accessors | no — you cast the result | `getString`, `getInt`, `getLong`, `getList`, `getMap`, `getObject` | | Config | inherited | accepts a `JsonPathConfig` overload | | Best for | a single field | several fields, or typed reads | Note where the typed getters live. `getList` is **not** a method on the extract side; the chain is `extract().jsonPath().getList("gainCurve")`. Reaching for `extract().getList(...)` is a common mis-attribution and it does not compile. ## Choosing the extractor deliberately 1. One field, obvious content type — `extract().path("fittingId")` is the right call and the clearest to read. 2. Several fields from one response — take `extract().jsonPath()` once and query it per field. 3. A typed read — `jsonPath().getInt("session.count")` beats casting the result of `path(...)`. 4. XML or HTML where you do not want sniffing — call `extract().xmlPath()` or `extract().htmlPath()` and pin the engine yourself rather than depending on what the service put in `Content-Type`. 5. The whole response, to hand to a helper or query later — `extract().response()`. ## The formatted-path overload `path` takes varargs: `extract().path("earFittings.%s.serial", side)` runs `String.format` over the path before evaluating it. It is convenient for a path that varies by a test parameter, and it is worth being explicit that this is plain string formatting of the expression — a value spliced in this way becomes part of the expression itself, so keep it to values your own test controls. ## Practical shape for a fitting API Reading a status, a device serial and a verification timestamp out of one `GET /fittings/{id}` response is three fields. Written with three `path(...)` calls it parses three times and reads as three unrelated statements; written with one held `JsonPath` it parses once and reads as one block against one object. - Extract once, after the expectations that guard the response. - Hold the path object in a local rather than re-deriving it. - Prefer the typed getter over a cast when the field has a natural type. - Fall back to `path(...)` for the genuinely single-field case, where the extra local costs more than it saves. None of this changes what the path *expression* means — that is the same expression language either way. What changes is how many times the document is parsed to answer it, and how much of the reading you do through the type system rather than through a cast.
- When would you call extract().xmlPath() rather than letting extract().path() decide?Whenever you do not want the response's `Content-Type` to choose the engine — a service that labels an XML body as `text/plain`, or one whose content type you would rather not depend on. `xmlPath()` and `htmlPath()` pin the parser explicitly, and `xmlPath()` has a compatibility-mode overload, so the read no longer breaks when a header changes.
- What does extract().path() do when the response has no content type at all?It throws `IllegalStateException`, because it cannot decide which engine to use. The message points at the fix: set `RestAssured.defaultParser`, or register a parser for the content type the service actually sends. Calling `extract().jsonPath()` directly also sidesteps the sniffing, since it names the engine for you.
saying these in an interview costs you the question
- Thinks extract().path() reuses a parsed document cached on the response
- Calls extract().getList(...) — getList is on JsonPath, not the extract side
- Believes path() works on any response regardless of content type
- Assumes jsonPath() and path() differ only in style, not in cost
- Reaches for asString() and manual string searching instead of a path object