REST URLs carry information in two places: the path (which resource) and the query string (how to filter, sort, or paginate that resource). The API Testing Masterclass lesson on query and path parameters explained the distinction in concept; this lesson is the Rest Assured way to encode it. The right choice keeps your URLs readable, your special characters encoded, and your tests resilient to URL formatting changes you'd never notice if you hand-concatenated everything as strings.
Path parameters — identifying the resource
Path parameters are placeholders in the URL template that get filled in at request time. Rest Assured uses curly-brace notation for the placeholder and pathParam(name, value) to fill it:
@Test
public void getOrderForUser() {
given()
.pathParam("userId", 42)
.pathParam("orderId", 100)
.when()
.get("/users/{userId}/orders/{orderId}") // → /users/42/orders/100
.then()
.statusCode(200)
.body("id", equalTo(100));
}Two reasons to do it this way rather than String url = "/users/" + userId + "/orders/" + orderId;:
- URL encoding for free. A user with an email-style ID like
alice@test.comwould break a hand-concatenated URL.pathParampercent-encodes the value automatically. - Logging and debugging clarity. When Rest Assured logs the request, it shows both the template and the resolved URL. You can see at a glance which value went into which slot.
You can also use pathParams(Map<String, ?>) to set several at once:
Map<String, Object> ids = Map.of("userId", 42, "orderId", 100);
given()
.pathParams(ids)
.when()
.get("/users/{userId}/orders/{orderId}");Query parameters — filtering and pagination
Query parameters live after the ? in the URL. They're for filtering, sorting, paginating, or anything else that modifies a request without changing which resource it's about:
@Test
public void getActiveAdminUsersFirstPage() {
given()
.queryParam("role", "admin")
.queryParam("active", true)
.queryParam("page", 1)
.queryParam("limit", 10)
.when()
.get("/users")
// Resolves to: /users?role=admin&active=true&page=1&limit=10
.then()
.statusCode(200)
.body("users.size()", lessThanOrEqualTo(10));
}The order in which you call queryParam(...) is the order they're appended — but no API should depend on parameter order, so don't sweat it.
Multiple values for the same parameter
Some APIs accept the same parameter repeated — ?status=pending&status=processing. Pass the values as varargs:
given()
.queryParam("status", "pending", "processing", "shipped")
.when()
.get("/orders");
// Resolves to: /orders?status=pending&status=processing&status=shippedOther APIs encode the same idea as a comma-separated list — ?status=pending,processing. Read the API's docs; Rest Assured supports both shapes via queryParam with one or many values.
Parameters from a Map
When you have parameters in a Map (e.g., loaded from a config, generated by a faker), pass the Map directly:
import java.util.HashMap;
import java.util.Map;
Map<String, Object> params = new HashMap<>();
params.put("page", 1);
params.put("limit", 20);
params.put("sort", "name");
params.put("order", "asc");
given()
.queryParams(params)
.when()
.get("/products")
.then()
.statusCode(200);Useful for parameterised tests where the Map is the test data row.
Path vs query — a one-line rule
The litmus test: if I removed this parameter, would the URL still point at a sensible resource? If yes, it's a query param. If no, it's a path param.
Encoding — the trap that bites everyone
Query parameter values often contain characters that need URL-encoding: spaces, +, &, ?, non-ASCII. Rest Assured encodes them by default, so this works:
given()
.queryParam("name", "John & Jane Doe")
.queryParam("city", "São Paulo")
.when()
.get("/users");
// Resolves to: /users?name=John%20%26%20Jane%20Doe&city=S%C3%A3o%20PauloIf you ever see encoded values arriving at the server double-encoded (e.g., the server logs show %2520 instead of %20), it usually means you encoded the value yourself before passing it to Rest Assured. Don't pre-encode — let the library do it.
For values that are already URL-encoded and you want to keep them literal, use:
given()
.urlEncodingEnabled(false)
.queryParam("token", "abc%2Bdef")That's a niche escape hatch. Default behaviour (encode automatically) is what you want 99% of the time.
Negative tests with parameters
Parameter handling is a rich source of bugs. Things every parameterised endpoint deserves a test for:
@Test
public void getUserWithNonNumericIdReturns400() {
given()
.pathParam("userId", "abc")
.when()
.get("/users/{userId}")
.then()
.statusCode(400); // or 404, depending on API design
}
@Test
public void getUsersWithEmptySearchReturnsAll() {
given()
.queryParam("search", "")
.when()
.get("/users")
.then()
.statusCode(200)
.body("size()", greaterThan(0));
}
@Test
public void getUsersWithExcessivePageSizeReturns400() {
given()
.queryParam("limit", 100000)
.when()
.get("/users")
.then()
.statusCode(400);
}
@Test
public void getUsersWithSqlInjectionInSearchIsSafelyHandled() {
given()
.queryParam("search", "Alice'; DROP TABLE users; --")
.when()
.get("/users")
.then()
.statusCode(anyOf(is(200), is(400)));
// Either is fine — but the table had better still exist.
}These are the bugs your suite should catch before a security review does. The API Testing Masterclass lesson on positive/negative/edge cases covers the broader strategy; here you're encoding it.
A complete parameterised test
import org.testng.annotations.Test;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;
public class OrderApiTest {
@Test
public void getOrderById() {
given()
.pathParam("userId", 1)
.pathParam("orderId", 7)
.queryParam("includeItems", true)
.header("Accept", "application/json")
.when()
.get("/users/{userId}/orders/{orderId}")
.then()
.statusCode(200)
.body("id", equalTo(7))
.body("userId", equalTo(1))
.body("items", hasSize(greaterThanOrEqualTo(1)));
}
}Two path params, one query param, one header — composed in one fluent block. The chain reads from top to bottom: which resource, how to fetch it, what to expect. That's the rhythm of every well-formed Rest Assured test.
⚠️ Common mistakes
- Hand-concatenating URLs.
String url = "/users/" + userId + "?role=" + role;works untiluserIdis1; DROP TABLE, orroleisadmin user. URL encoding, special characters, and traceable logging are whypathParamandqueryParamexist. - Confusing path and query for filtering.
GET /users/admin/activemixes a filter (role=admin) into the path. The cleaner shape isGET /users?role=admin&active=true. Tests that hit the wrong shape often fail in confusing ways because the API also has/users/{id}and youradminends up being interpreted as an ID. - Pre-encoding parameter values. Calling
queryParam("name", URLEncoder.encode("Jane Doe", UTF_8))results in double-encoding when Rest Assured encodes the already-encoded value. Pass raw values; trust the library.
🎯 Practice task
Drive every parameter pattern against REQRES — a free fake user API that supports paging. 30–35 minutes.
- Set
RestAssured.baseURI = "https://reqres.in"in@BeforeClass. - Write
getUserById()— GET/api/users/2using a path parameter. Assert the email is"janet.weaver@reqres.in". - Write
getUsersPage2()— GET/api/userswithqueryParam("page", 2). Assertpagefield equals2anddataarray has expected size. - Write
getUsersPage2WithDelay()— same call but addqueryParam("delay", 3). The API will take 3 seconds. Add.time(lessThan(5000L))and confirm green. - Multiple parameters. Combine path + query + header in one test: GET
/api/users/2?delay=1withAccept: application/json. Assert the email and that response time is under 3 seconds. - Parameters from a Map. Build a
Map<String, Object>of{page=1, per_page=3}and pass viaqueryParams(map). Assert the response'sper_pagematches what you sent. - Negative case. GET
/api/users/abc(non-numeric ID). Assert a4xxstatus. Note how the API handles it — REQRES returns404for unknown users. - Stretch: look at the request log for one of your tests. Add
.log().all()insidegiven()(orRestAssured.filters(new RequestLoggingFilter())in@BeforeClass). Run a test and read the resolved URL — confirm your parameters are encoded correctly.
Next lesson: headers and content types — how the request and response negotiate format, and the half-dozen headers every API tester reaches for.