skip to content

Your REST Assured SessionFilter returns null from getSessionId() after a successful login. Why?

level: seniorimportance: must knowfreq 47%

answer

  1. capture is a lookup by name
  2. JSESSIONID is only a default
  3. blank values are never stored
  4. log the sign-in response cookies
  5. one instance, on both calls

basics

~20 s

Almost always the session cookie is not named JSESSIONID. SessionFilter stores Response.sessionId(), which looks up the cookie named by SessionConfig.sessionIdName(), so a differently named cookie yields null, nothing is captured, and later requests go out with no session at all.

solid answer

~50 s

`SessionFilter` captures by calling `Response.sessionId()`, and that is a lookup of the cookie named by `SessionConfig.sessionIdName()` — `JSESSIONID` unless configured otherwise. If the beehive telemetry API sets `APIARY_SESSION` instead, the lookup returns `null`, the filter stores nothing because it only stores non-blank values, `hasSessionId()` stays false, and every later call goes out anonymous and `401`s. The fix is to tell REST Assured the name: `RestAssured.config = config().sessionConfig(new SessionConfig().sessionIdName("APIARY_SESSION"))`, which fixes both the read and the send side. A `CookieFilter` is the alternative — it carries whatever cookies the server set, whatever they are called. The other usual causes are a fresh filter instance per request, or the filter never being added to the sign-in call at all. Split the diagnosis before you touch anything: `hasSessionId()` straight after sign-in tells you whether the capture or the replay is the half that broke.

code

java · 38 lines
java
import io.restassured.RestAssured;
import io.restassured.config.SessionConfig;
import io.restassured.filter.session.SessionFilter;
import io.restassured.http.ContentType;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;

import static io.restassured.RestAssured.given;
import static org.junit.jupiter.api.Assertions.assertTrue;

class ApiarySessionNameTest {

    @AfterEach
    void tearDown() {
        RestAssured.reset();
    }

    @Test
    void capturesASessionCookieThatIsNotCalledJsessionid() {
        RestAssured.config = RestAssured.config()
                .sessionConfig(new SessionConfig().sessionIdName("APIARY_SESSION"));

        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)
            .log().cookies();

        assertTrue(apiarySession.hasSessionId());
    }
}

go deeper

for a junior

Know that a SessionFilter has to be on the sign-in request too, and that it only understands a cookie called JSESSIONID until you configure a different name.

for a middle

Explain the capture path: the filter reads Response.sessionId(), that is a lookup of the configured name, and only a non-blank result is stored.

for a senior

Drive the diagnosis in order — capture or replay, then name, placement, instance lifetime — and pick between configuring SessionConfig and switching to a CookieFilter with a reason.

for a principal

Own the standard for the suite: where the session cookie name is declared, who configures it, and how a name change on the API surfaces as one clear failure rather than dozens.

## The capture path, step by step A `SessionFilter` is added to a request with `given().filter(sessionFilter)`. When the call completes, the filter takes the returned `Response` and asks it for `sessionId()`. If that value is not blank, it stores it in an internal reference; if it is blank or `null`, it stores nothing and keeps whatever it had before. On a later request the filter sets the stored id — but only if the request does not already carry a cookie of that name. The whole diagnosis of a null `getSessionId()` therefore lives in one question: **what does `Response.sessionId()` return on your sign-in response?** ## Why it returns null: the name lookup `Response.sessionId()` is not "the session cookie" in any clever sense. It reads the response cookie whose **name** is `SessionConfig.sessionIdName()`. That defaults to `SessionConfig.DEFAULT_SESSION_ID_NAME`, the literal string `JSESSIONID`, and the response object is stamped with the active name at request time. So if `POST /apiary/session` comes back with `Set-Cookie: APIARY_SESSION=...`, the lookup finds nothing, `sessionId()` is `null`, and the filter is doing exactly what it was written to do — silently. There is no warning, no exception, and the second call fails somewhere else entirely, which is what makes this a genuinely annoying half-hour. ## The four causes, in the order they are worth checking 1. **The cookie name does not match.** The most common by a distance. Confirm it by logging the sign-in response cookies and reading the name off the wire. 2. **The filter was not on the sign-in call.** A filter only sees requests it is attached to. If `given().filter(sessionFilter)` is on the telemetry read but not on `POST /apiary/session`, there was never a response to capture from. 3. **A fresh instance per request.** The captured value lives in the instance. `new SessionFilter()` inside each test method or each helper call gives you a filter with nothing in it, every time. 4. **The server did not set a cookie at all.** Some sign-in endpoints hand the credential back somewhere other than a cookie; a cookie-carrying filter has nothing to work with then, and the whole mechanism is the wrong tool. ## Confirming the name from the response itself ```java Response signIn = given() .contentType(ContentType.JSON) .body("{\"keeper\":\"ada\",\"passphrase\":\"combsecret\"}") .when() .post("/apiary/session") .then() .statusCode(200) .log().cookies() .extract().response(); System.out.println(signIn.getCookies().keySet()); ``` `then().log().cookies()` is on the shared half of the log DSL, so it works on the response side, and it prints the names you actually got. That output settles cause 1 in one run. ## The two fixes, and what each one costs | fix | what it changes | when it is the right call | | --- | --- | --- | | `SessionConfig.sessionIdName("APIARY_SESSION")` | both `Response.sessionId()` and `given().sessionId(...)` use that name | the API has exactly one session cookie with a stable name | | use a `CookieFilter` instead | every cookie the server set is stored and replayed, name-agnostic | the logged-in state is more than one cookie, or names vary | Configuring the name is the smaller change and keeps `sessionId()`, `getSessionId()` and `given().sessionId(...)` all meaningful. Apply it globally when the whole suite talks to one API: ```java RestAssured.config = RestAssured.config() .sessionConfig(new SessionConfig().sessionIdName("APIARY_SESSION")); ``` or scope it to one call with `given().config(...)` when only part of the suite needs it. Note that `SessionConfig` is immutable in the usual REST Assured style: `sessionIdName(...)` returns a new instance rather than mutating the one you called it on, so assigning the result is not optional. Swapping to a `CookieFilter` is the bigger hammer and is right when the beehive telemetry API sets, say, both a session cookie and a tenant-selection cookie, and both have to come back. Its `getCookieStore()` exposes the underlying store if you want to inspect or seed what it holds. ## What not to try - Calling `storeSessionIdFromResponse(response)` by hand does **not** help: it goes through the same `Response.sessionId()` lookup and will store the same `null`. - Adding a second `SessionFilter` does not help either; both instances read the same configured name and both come back empty. - Reading `extract().cookie("APIARY_SESSION")` and passing it forward manually does work, but you have then rebuilt the filter by hand and given up the automatic replay. ## The habit worth keeping When a session-carrying suite fails on its second request, split the question in two before touching anything: was the id **captured**, and was it **applied**? `getSessionId()` answers the first, and logging the second request's cookies answers the second. Almost every failure of this shape is a capture failure, and almost every capture failure is a name mismatch.

  • How would you prove the failure is in the capture rather than in the replay?
    Assert `sessionFilter.hasSessionId()` straight after the sign-in call. False means nothing was captured, so the cookie name or the filter placement is wrong. True means capture worked and the next thing to check is whether the second request already carries that cookie, which makes the filter stand down.
  • When would you choose a CookieFilter over configuring the session id name?
    When the logged-in state is not one cookie. If the beehive API sets a session cookie plus a tenant or region cookie and both must return, a `CookieFilter` stores and replays all of them by name, matched against the request URI, whereas the session mechanism only ever carries one configured name.

saying these in an interview costs you the question

  • Blaming the server before checking the configured cookie name
  • Calling storeSessionIdFromResponse by hand and expecting a different result
  • Adding a second SessionFilter to catch the other cookie name
  • Assuming a blank capture will still be replayed as an empty cookie
  • Forgetting to attach the filter to the sign-in request itself