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 the request:
req.setSessionAttr("user", user); // creates the session if there is none yet
User user = req.sessionAttr("user", User.class); // null when absent
User same = req.sessionAttr("user"); // the same read, cast by the caller
req.removeSessionAttr("user"); // creates no session to remove from
Reading never 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.
Name the type to be told where a wrong one is.
sessionAttr(key, User.class) casts with Class.cast and fails on that line, naming the key, the type found, and the type asked for.
sessionAttr(key) casts at the call site instead, so the same mistake is a ClassCastException on the assignment rather than on the read.
Both are IllegalStateException territory rather than a 400: the value was put in the session by the application itself, so a type it does not expect is the application’s own mistake and answers 500.
Neither is reflection in the sense this framework avoids, since the type is written at the call site.
The write is named setSessionAttr, and every read is named sessionAttr.
No argument turns one into the other, so a Class is stored like any other value.
A literal null passed as the type to sessionAttr(key, type) throws NullPointerException, and the message names removeSessionAttr(key).
Writing a null value removes the attribute, which is the container’s rule for HttpSession.setAttribute.
setSessionAttr(key, null) and removeSessionAttr(key) therefore do the same thing, and only the second says so by name.
Ending a session
invalidateSession() ends the session, which is what logging out is:
app.post("/logout", req -> {
req.invalidateSession();
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. Invalidate and then return: a session attribute read after this answers null, and one written after it starts a session again.
Flash
A flash value is visible exactly once, on the request after the one that set it. That is the missing piece of the Post/Redirect/Get pattern: the POST wants to say "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").asText());
req.flash("message", "Imported " + imported + " cards.");
return WebResponse.redirect("/decks/" + req.pathParam("deckId"), HttpStatus.SEE_OTHER);
});
app.get("/decks/{deckId}", req -> WebResponse.template("deck",
Map.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.
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 what waits 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.