skip to content

In REST Assured, when will a SessionFilter or CookieFilter refuse to apply the session it captured?

level: seniorimportance: should knowfreq 39%

answer

  1. the filters check before they write
  2. first writer wins, not last
  3. config value is applied last, ranks lowest
  4. only sessionId() actually replaces
  5. CookieFilter(true) opts out of the check

basics

~20 s

Whenever the request already carries a cookie of that name. Both filters check the request specification first and stand down rather than overwrite, as does the session id value held in SessionConfig. Only given().sessionId() replaces an existing cookie outright.

solid answer

~50 s

Both filters look at the outgoing spec before they touch it. A `SessionFilter` applies its stored id only when `requestSpec.getCookies().hasCookieWithName(sessionIdName)` is false. A `CookieFilter` applies each stored cookie only when the request has no cookie of that name, unless you built it as `new CookieFilter(true)` to allow duplicates. `SessionConfig`'s own `sessionIdValue` — where `RestAssured.sessionId` ends up — is applied last, inside the send step, under the same condition. So the precedence is call order plus first-writer-wins: an explicit `given().sessionId("expired")` on `GET /hives/h-42/telemetry` still produces a `401` with a healthy `SessionFilter` attached. The flip side is that a stray cookie left on a shared spec silently suppresses the filter's fresh id, with no warning anywhere. There is no allow-duplicates flag on `SessionFilter` either, so the only cure for that is to stop putting the cookie on the spec rather than to fight the filter.

code

java · 33 lines
java
import io.restassured.filter.session.SessionFilter;
import io.restassured.http.ContentType;
import org.junit.jupiter.api.Test;

import static io.restassured.RestAssured.given;

class HiveSessionPrecedenceTest {

    @Test
    void explicitSessionIdBeatsTheFilterThatHoldsALiveOne() {
        SessionFilter apiarySession = new SessionFilter();

        given()
            .baseUri("https://hives.example.test")
            .filter(apiarySession)
            .contentType(ContentType.JSON)
            .body("{\"keeper\":\"ada\",\"passphrase\":\"combsecret\"}")
        .when()
            .post("/apiary/session")
        .then()
            .statusCode(200);

        given()
            .baseUri("https://hives.example.test")
            .filter(apiarySession)
            .sessionId("expired-hive-session")
            .log().cookies()
        .when()
            .get("/hives/h-42/telemetry")
        .then()
            .statusCode(401);
    }
}

go deeper

for a junior

Remember that setting a session id yourself takes priority over a filter. If both are in play, the value you wrote in the test is the one that goes out.

for a middle

Explain the check each mechanism performs on the request's existing cookies, and why the configuration value ranks lowest even though it is applied last.

for a senior

Use the rule for negative tests, and recognise its failure mode: a stale cookie on a shared spec that silently suppresses a filter's fresh session on every call.

for a principal

Decide whether a suite should ever mix explicit session values with a capturing filter, and how you would make a suppressed session fail loudly instead of quietly.

## Three writers, one cookie slot By the time a REST Assured request goes out, up to three different mechanisms may want to put a session cookie on it, and they run at different moments: 1. **The DSL**, as you build the spec — `given().sessionId(...)` or `given().cookie(...)`. 2. **A filter**, when the filter chain runs — `SessionFilter` or `CookieFilter`. 3. **The configuration**, inside the internal send step — `SessionConfig.sessionIdValue`, which is where an assignment to `RestAssured.sessionId` is folded when `given()` is called. Every one of them after the first checks whether the slot is taken and, if it is, does nothing. The result is a **first-writer-wins** rule that is not written down anywhere obvious in the DSL. ## What each one checks - `SessionFilter` applies its stored id only when `requestSpec.getCookies().hasCookieWithName(config.getSessionConfig().sessionIdName())` is false. It also stores the response's session id afterwards regardless, so a request that ignored the filter can still refresh it. - `CookieFilter` walks its cookie store and, for each stored cookie that matches the request URI, applies it only when the request has no cookie of that name. Constructing it as `new CookieFilter(true)` — the `allowMultipleCookiesWithTheSameName` flag — turns that check off and lets a stored cookie ride alongside one you set explicitly. - The `SessionConfig` value is applied in the send step, again only when no cookie of that name is already on the spec. - `given().sessionId(name, value)` is the one exception: it evicts any same-named cookie and replaces it. It is the only mechanism here that overwrites. ## The precedence table | set by | runs | overwrites? | effective rank | | --- | --- | --- | --- | | `given().sessionId(...)` / `given().cookie(...)` | while the spec is built | `sessionId` replaces; `cookie` appends | highest | | `SessionFilter` / `CookieFilter` | in the filter chain | no — stands down | middle | | `SessionConfig.sessionIdValue` / `RestAssured.sessionId` | in the send step | no — stands down | lowest | Note that this is the opposite of the intuition "the thing that runs last wins". Running last is exactly why the config value has the least authority: by then, anybody else has already claimed the slot. ## Where the rule helps you The obvious win is negative testing. A beehive telemetry suite that carries a live session through a `SessionFilter` can still assert the unauthenticated path without unwiring anything: ```java given() .filter(apiarySession) .sessionId("expired-hive-session") .when() .get("/hives/h-42/telemetry") .then() .statusCode(401); ``` The filter holds a perfectly valid id, sees `JSESSIONID` already on the request, and yields. The same is true with `RestAssured.sessionId` set globally. Without the rule you would have to build a separate spec with no filter just to test a rejection. ## Where the rule hurts you The failure mode is the mirror image, and it is quiet. Suppose a shared `RequestSpecification` for the beehive API was built with a session id in it at some point, and later somebody adds a `SessionFilter` to refresh the session per test. Every request now carries the stale cookie from the spec, the filter stands down on every call, and the freshly captured id is never used. Nothing logs a conflict; the tests just fail against a session that expired last Tuesday. Symptoms worth recognising: - `sessionFilter.hasSessionId()` is `true` and the request still `401`s. - Logging the outgoing cookies shows a value that does not match `sessionFilter.getSessionId()`. - Removing an unrelated-looking `.cookie(...)` or `.sessionId(...)` line makes the suite pass. ## How to diagnose it in one pass 1. Print what the filter holds: `sessionFilter.getSessionId()`. 2. Print what actually went out: add `given().log().cookies()` to the failing request. 3. Compare. If they differ, something claimed the slot before the filter ran, and step three is to find which layer put it there — the inline chain, an attached spec, or a globally configured session id. ## When you genuinely want both `new CookieFilter(true)` is the escape hatch when the server legitimately expects two cookies of the same name for different scopes and you want the stored one sent alongside your explicit one. Reach for it deliberately and rarely; the default of `false` exists because sending two same-named cookies is much more often a bug than a design. There is no equivalent flag on `SessionFilter` — it carries exactly one id and always yields. If you need a request to use a captured session even though the spec already names that cookie, the fix is to stop putting the cookie on the spec, not to fight the filter. ## The short version Nothing in REST Assured's session machinery ever overwrites a cookie somebody else already put on the request. That makes explicit values reliable and makes stale ones invisible, and both halves of that trade show up in real suites.

  • A SessionFilter reports hasSessionId() true and the request still returns 401. What now?
    Compare `getSessionId()` with the cookies actually sent, via `given().log().cookies()`. If they differ, something claimed the slot before the filter ran — an inline `.cookie(...)`, an attached spec carrying a stale id, or a globally configured session id — and the filter stood down as designed.
  • Why does the configuration-level session id have the lowest precedence despite running last?
    Because it is applied conditionally. The send step adds `SessionConfig.sessionIdValue` only when no cookie of that name is on the spec, so by the time it runs the DSL and any filter have already had their chance. Running last means it sees a slot that is usually already claimed.

It behaves like a numbered seat that is already occupied. Each mechanism checks whether that cookie name is taken and walks past rather than turfing anyone out.

saying these in an interview costs you the question

  • Assuming the filter overwrites whatever the request already set
  • Believing the mechanism that runs last necessarily wins
  • Thinking a conflicting session cookie triggers a warning or error
  • Expecting CookieFilter to replace a same-named cookie by default
  • Looking for an allow-duplicates flag on SessionFilter