Sessions and Flash

A session keeps values on the server across a visitor’s requests. A flash value is a session value that survives exactly one request more.

Session attributes

Session attributes are read and written through req.session(), a WebSession that holds every session operation in one place:

req.session().set("user", user); // creates the session if there is none yet
User user = req.session().get("user", User.class); // null when absent
User same = req.session().get("user"); // the same read, cast by the caller
req.session().remove("user"); // creates no session to remove from

Neither session() nor a read creates a session, so a visitor who has not signed in costs no session object. Writing creates one on demand. The value is any object and stays on the server: only the container’s session cookie travels. That cookie is HttpOnly on Jetty, Tomcat, and Undertow alike, so a script on the page cannot read it.

Passing the type makes a wrong type fail on the line that reads it. get(key, User.class) casts with Class.cast and fails on that line with an IllegalStateException naming the key, the type found, and the type asked for. get(key) casts at the call site instead, so the same mistake is a ClassCastException on the assignment rather than on the read. Either mistake answers 500, not 400: the application put the value in the session itself, so a type it does not expect is its own mistake. Neither is reflection in the sense this framework avoids, since the type is written at the call site.

The write is named set, and every read is named get. No argument turns one into the other, so a Class is stored like any other value. A literal null passed as the type to get(key, type) throws NullPointerException, and the message names remove(key).

Writing a null value removes the attribute, which is the container’s rule for HttpSession.setAttribute. set(key, null) and remove(key) therefore do the same thing, and only the second says so by name.

Ending a session

session().invalidate() ends the session, which is how an application logs a visitor out:

app.post("/logout", req -> {
    req.session().invalidate();
    return WebResponse.redirect("/", HttpStatus.SEE_OTHER);
});

Everything in the session is gone and the visitor’s next request starts a new one. A request with no session to begin with is left alone, so logging out twice is not an error. Return right after invalidating: a session attribute read after the call answers null, and one written after it starts a new session.

Sessions come from the servlet container and are on by default. JettyServer turns it off with sessions(false). Tomcat and Undertow always install a session manager, as their pages explain.

Flash

A flash value is visible exactly once, on the request after the one that set it. Flash fills the gap in the Post/Redirect/Get pattern: the POST wants to show "Imported 12 cards." on the page its redirect lands on.

app.post("/decks/{deckId}/import", req -> {
    int imported = deckService.importCsv(req.pathParamLong("deckId"), req.file("file").text());
    req.flash("message", "Imported " + imported + " cards.");
    return WebResponse.redirect("/decks/" + req.pathParam("deckId"), HttpStatus.SEE_OTHER);
});

app.get("/decks/{deckId}", req -> WebResponse.template("deck",
        Model.of("message", req.flashed("message"), "deck", ...)));

flash(key, value) stores a string in the session. Before the next request is dispatched, everything flashed moves out of the session and into that request, where flashed(key) reads it. Any request after that finds nothing. flashed answers null for a key nobody flashed, so a template can render the message conditionally. Model.of takes that null as an entry, as Templates describes. Flash rides on the session, so it needs sessions on, which they are by default.

flash(key, null) removes the key, the same rule a null session attribute follows. A handler withdraws a flash it set earlier in the same request this way:

req.flash("message", "Imported " + imported + " cards.");
if (imported == 0) {
    req.flash("message", null); // the next request reads null
}

The removal applies only to the value waiting for the next request. A value this request received stays readable through flashed(key). Withdrawing a flash with no session in place does not start one.