A REST Assured call fails with IllegalArgumentException: Invalid number of path parameters — how do you diagnose it?
answer
- placeholders counted against values supplied
- thrown at send, not at given
- read the redundant and undefined lists
- a second wording means names, not counts
basics
~20 sREST Assured compares the placeholders it found in the path with the named and positional values supplied, and the exception message names both halves: redundant values with no placeholder, and undefined placeholders with no value. Read those two lists first.
solid answer
~50 sThe check runs when the request is sent, so the stack trace points at the verb rather than at the `given()` that actually caused the problem. The message is a full accounting: `Invalid number of path parameters. Expected 2, was 3.` gives the counts, `Redundant path parameters are: ...` lists values that matched no placeholder, and `Undefined path parameters are: ...` lists placeholders that received no value. A second wording, `Path parameters were not correctly defined.`, means the counts balanced but the **names** did not — usually a typo between `pathParam("orderId", ...)` and a template written `{orderID}`. The frequent causes are shared setup contributing a named parameter to a path that never declares it, one trailing argument too many, and a literal brace in the path being read as a placeholder. Comparing the two lists normally settles it in seconds.
code
java · 15 linesimport static io.restassured.RestAssured.given;
// Two placeholders, three values supplied:
// IllegalArgumentException: Invalid number of path parameters.
// Expected 2, was 3. Redundant path parameters are: MOROCCO.
given()
.get("/orders/{orderId}/signatures/{signatureNo}", "BND-4417", 12, "MOROCCO");
// Counts balance, names do not:
// IllegalArgumentException: Path parameters were not correctly defined.
// Redundant path parameters are: orderID=BND-4417.
// Undefined path parameters are: orderId.
given()
.pathParam("orderID", "BND-4417")
.get("/orders/{orderId}");go deeper
Read the whole exception message rather than only its first sentence. The redundant and undefined lists usually name the mistake, and the counts tell you which side has too much.
Explain that placeholders are counted against named plus positional values at send time, and distinguish the counts-wrong wording from the names-wrong one with an example of each.
Work it as a suite problem: trace a redundant named parameter back to shared setup, know that filters run before the check, and say why repairing it in a filter is a worse answer than fixing the setup.
Own how the suite fails. Decide whether path templates are centralised so a shape change is one edit, and what standard keeps a shared specification from contributing parameters most endpoints never declare.
## How REST Assured counts path parameters Before a request goes out, REST Assured builds two sets and compares them. The first is the **placeholders** it found by scanning the target path for `{` ... `}` pairs. The second is the **values** you supplied — the named ones from `pathParam` and `pathParams`, plus the positional ones passed as trailing arguments to the verb. If either set has a member the other cannot account for, it throws `IllegalArgumentException` rather than sending a request to a URL that still contains braces. Crucially the check happens **during the send**, not while you are building the specification. A stack trace therefore points at `get(...)` or `post(...)`, while the mistake usually lives in a `given()` several lines — or several files — earlier. ## Reading the message The exception text is built from up to three parts, and each part answers a different question: | fragment | what it tells you | |---|---| | `Expected 2, was 3.` | the raw counts — which side has too much | | `Redundant path parameters are: ...` | values that matched no placeholder, named ones as `name=value` and positional ones as bare values | | `Undefined path parameters are: ...` | placeholders that received no value, by name | A typical one reads in full: `Invalid number of path parameters. Expected 2, was 3. Redundant path parameters are: MOROCCO.` That single line tells you the template has two slots, three values arrived, and the loose one is `MOROCCO` — which is enough to find the call site without a debugger. ## The second wording When the counts balance but the names do not, the message opens differently: `Path parameters were not correctly defined.` followed by both lists. For example, `given().pathParam("orderID", "BND-4417").get("/orders/{orderId}")` yields a redundant `orderID=BND-4417` and an undefined `orderId` at the same time. One value, one placeholder, and nothing lines up. Recognising this wording saves real time, because the arithmetic looks correct and the instinct is to go hunting for a missing argument that does not exist. ## The usual causes 1. **Shared setup contributes a name the path does not declare.** A helper that adds `pathParam("binderyId", ...)` for the endpoints that need it will make every other endpoint fail with a redundant named parameter. 2. **One trailing argument too many or too few.** Easy to introduce when a template gains or loses a segment and only some call sites are updated. 3. **A typo in a name.** Casing counts: `{orderId}` and `orderID` are different placeholders. 4. **A literal brace in the path.** Anything between braces in the target path is read as a placeholder, so a path fragment carrying JSON-ish or matrix-ish text becomes an undefined placeholder you never meant to declare. 5. **A named parameter that quietly claimed the slot a positional value was aimed at.** Because named values resolve first, adding one can leave a trailing value with nowhere to go. ## Where the check sits in the lifecycle The count check happens inside the send, downstream of the filters registered on the request. That ordering is useful rather than incidental: a filter receives a `FilterableRequestSpecification`, which exposes `removePathParam`, `removeNamedPathParam`, `removeUnnamedPathParam` and `removeUnnamedPathParamByValue` alongside the ordinary setters. A redundant value contributed by shared setup can therefore be dropped on the way out, before the accounting sees it. That is a legitimate escape hatch for an awkward shared specification, though it is a worse first answer than fixing the setup — a filter that silently removes parameters makes the next person's diagnosis harder, not easier. ## A different exception for a null value One related failure is not this check at all. A `null` passed positionally is rejected as the request method is invoked, with `IllegalArgumentException: Unnamed path parameter cannot be null (path parameter at index 0 is null)`. When several are null the message lists the indices together. Two things distinguish it: it names an **index** rather than a placeholder name, and it fires earlier than the counting check, so the accounting message never appears. In practice it means a fixture returned nothing and the test carried on regardless — the fix belongs in the fixture, not in the request. ## Working the failure A reliable order of attack when this lands in CI: - Read the two lists in the message before opening any source. They frequently name the mistake outright. - If the counts balance but the wording is `Path parameters were not correctly defined.`, look for a casing or spelling difference rather than a missing value. - Check what shared setup contributes before you check the test. A redundant named parameter almost always comes from somewhere the test does not mention. - Print the template and the parameters together. `given().log().uri()` will not help here, because the exception fires before a URI can be built — log the values at the call site instead. The reassuring part is that this exception is loud by design. The genuinely dangerous path-parameter bug is the one where the counts balance, the names match, and the value simply lands in the wrong slot.
- What exception do you get for a null positional path value, and how does it differ?A different and earlier one: `IllegalArgumentException: Unnamed path parameter cannot be null (path parameter at index 0 is null)`, thrown as the request method is invoked rather than during the send. It names the offending index — or lists several indices together when more than one is null — so you do not have to count arguments yourself to find which fixture came back empty.
- Where does the count check sit relative to the filters on a request?Inside the send, after the registered filters have run. That is why a redundant value can be repaired on the way out: a filter holds a `FilterableRequestSpecification`, which exposes `removePathParam`, `removeNamedPathParam` and `removeUnnamedPathParamByValue` beside the ordinary setters. Useful as an escape hatch for an awkward shared specification, but it hides the cause from the next reader.
saying these in an interview costs you the question
- Blames the line in the stack trace instead of the given() setup
- Ignores the redundant and undefined lists in the message
- Thinks the check runs while the specification is being built
- Treats the names-mismatch wording as a missing argument
- Assumes a literal brace in the path is left alone