Spring: @SessionAttributes vs HttpSession Spring: @SessionAttributes vs HttpSession spring spring

Spring: @SessionAttributes vs HttpSession


@SessionAttributes enables persisting model attributes session between requests, and is meant to be specific per hanlder. The intent was to provide a construct that will be a step close towards implementing a conversation scope (shorter than session, longer than request). The need for conversational scope and why it is not fully available with @SessionAttributes is explained well in this blog.

It enables the automatic storing of the matching model attributes (match is based on the name). The default storage is HttpSession but this can be configured differently also. The docs say

Session attributes as indicated using this annotation correspond to a specific handler's model attributes, getting transparently stored in a conversational session. Those attributes will be removed once the handler indicates completion of its conversational session.

This bit however Those attributes will be removed once the handler indicates completion of its conversational session. does not happen automagically, and it is up to a developer to indicate the exit from the conversation by using setComplete on a SessionStatus instance. Otherwise the models attribute will remain in session, often an undesired side effect.

The easiest way to understand the difference is by observing the scope and the value of the model variable, model variable backed with @SessionAttribute, and the "normal" HttpSession variable.

Take a look at the two simple controllers

@Controller@SessionAttributes("modelAndSession")@RequestMapping("/sessionattr")public class FirstController {    protected static final String NEXT_VIEW = "next";    @RequestMapping("/init")    public String handlingMethod1( Model model, HttpSession session) {        model.addAttribute(NEXT_VIEW, "/sessionattr/afterinit");        session.setAttribute("session", "TRUE");        model.addAttribute("modelAndSession", "TRUE");        model.addAttribute("model", "TRUE");        return "index";    }    @RequestMapping("/afterinit")    public String handlingMethod2(SessionStatus status, Model model) {        model.addAttribute(NEXT_VIEW, "/nosessionattr/init");        //status.setComplete();        return "index";    }}

the second controller

@Controller@RequestMapping("/nosessionattr")public class SecondController {    protected static final String NEXT_VIEW = "next";    @RequestMapping("/init")    public String handlingMethod3(Model model) {        model.addAttribute(NEXT_VIEW, "/sessionattr/init");        return "index";    }}

and the view that will trigger the flow

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %><a href="${next}">Next step ${next}</a><hr/><table>     <thead>          <th>key</th> <th>Request scope</th> <th>Session scope</th>     </thead>    <tr>        <td>model</td> <td>${requestScope.model}</td> <td>${sessionScope.model}</td>    </tr>    <tr>        <td>model and session</td> <td>${requestScope.modelAndSession}</td> <td>${sessionScope.modelAndSession}</td>    </tr>    <tr>        <td>session</td> <td>${requestScope.session}</td> <td>${sessionScope.session}</td>    </tr></table>

The flow

Upon the initial request /sessionattr/init the view renders as follows

enter image description here

so the model variable is available in the request scope, the sessionattribute is available in both the request and the session scope and the "normal" session attribute available only in session scope

On the next request /sessionattr/afterinit the view renders as follows

enter image description here

so the model-only variable is gone, while the @SessionAttribute model attribute is pushed from the session to the model and persisted across requests. The next step will target a second controller /nosessionattr/init, and the view will render as follows

enter image description here

now the @SessionAttribute model object is gone from the model, but since the status.setComplete is not explicitely called it remained in the session as a normal variable

This is a specially confusing scenario as many expect that @SessionAttribute model object should be gone after switching handler, however unless explicitly cleared it remains in the session. Feel free to copy the snippets and investigate further the combination that confuses you


Master Slave has already answered the questions. There's one thing I would like to add about the difference between @SessionAttributes and HttpSession

Altough @SessionAttributes are saved in the HTTPSession - they will not disappear if calling HTTPSesssion#invalidate()There will be a new session, but the variables marked as @SessionAttributes will persist and be copied into the new session.

So if need be to invalidate the session, or just if the @SessionAttributes themselves are not needed anymore in the conversation scope, then also inject the SessionStatus-interface into an appropriate handler-method, then call SessionStatus#setComplete()

I also rewrote the JSP-file provided by Master Slave, into a Thymeleaf-template. Same controller-classes can be used.

<!DOCTYPE html><html><head>    <meta charset="UTF-8">    <title>Index</title></head><body><a href="../sessionattr/init" data-th-href="@{${next}}">Next step [[${next}]]</a><hr><table>    <thead>        <tr>            <th>key</th>            <th>Request Scope</th>            <th>Session Scope</th>        </tr>    </thead>    <tbody>        <tr>            <td>model</td>            <td data-th-text="${#request.getAttribute('model')?: 'false'}">N/A</td>            <td data-th-text="${#session.getAttribute('model')?: 'false'}">N/A</td>        </tr>        <tr>            <td>modelAndSession</td>            <td data-th-text="${#request.getAttribute('modelAndSession')?: 'false'}">N/A</td>            <td data-th-text="${#session.getAttribute('modelAndSession')?: 'false'}">N/A</td>        </tr>        <tr>            <td>session</td>            <td data-th-text="${#request.getAttribute('session')}?: 'false'">N/A</td>            <td data-th-text="${#session.getAttribute('session')}?: 'false'">N/A</td>        </tr>    </tbody></table></body></html>