Path and Query Parameters

8 min read

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.com would break a hand-concatenated URL. pathParam percent-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=shipped

Other 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%20Paulo

If 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 until userId is 1; DROP TABLE, or role is admin user. URL encoding, special characters, and traceable logging are why pathParam and queryParam exist.
  • Confusing path and query for filtering. GET /users/admin/active mixes a filter (role=admin) into the path. The cleaner shape is GET /users?role=admin&active=true. Tests that hit the wrong shape often fail in confusing ways because the API also has /users/{id} and your admin ends 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.

  1. Set RestAssured.baseURI = "https://reqres.in" in @BeforeClass.
  2. Write getUserById() — GET /api/users/2 using a path parameter. Assert the email is "janet.weaver@reqres.in".
  3. Write getUsersPage2() — GET /api/users with queryParam("page", 2). Assert page field equals 2 and data array has expected size.
  4. Write getUsersPage2WithDelay() — same call but add queryParam("delay", 3). The API will take 3 seconds. Add .time(lessThan(5000L)) and confirm green.
  5. Multiple parameters. Combine path + query + header in one test: GET /api/users/2?delay=1 with Accept: application/json. Assert the email and that response time is under 3 seconds.
  6. Parameters from a Map. Build a Map<String, Object> of {page=1, per_page=3} and pass via queryParams(map). Assert the response's per_page matches what you sent.
  7. Negative case. GET /api/users/abc (non-numeric ID). Assert a 4xx status. Note how the API handles it — REQRES returns 404 for unknown users.
  8. Stretch: look at the request log for one of your tests. Add .log().all() inside given() (or RestAssured.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.

// tip to track lessons you complete and pick up where you left off across devices.