Close

Spring MVC - Using HttpSession as Controller Method Argument

[Last Updated: Mar 31, 2026]

If for some reasons we cannot use Session Scoped beans (last tutorial) then we have another option of working with low level Servlet API to maintain session attributes. In this example we are going to use jakarta.servlet.http.HttpSession as a controller method argument. Spring supports this argument and it is never null. In the background, Spring uses HttpServletRequest#getSession() (check out ServletRequestMethodArgumentResolver #resolveArgument() method) which returns the session associated with the current request, or if the request does not have a session, a new session object is created.

Example

@Controller
public class MyController {

    @ResponseBody
    @RequestMapping(value = "/", produces = MediaType.TEXT_PLAIN_VALUE)
    public String handler(HttpSession httpSession) {
        String sessionKey = "sessionCounter";
        Object counter = httpSession.getAttribute(sessionKey);
        long counterLong = counter == null ? 1L :
                ((Long) counter) + 1;
        httpSession.setAttribute(sessionKey, counterLong);
        return "session counter : " + counterLong + "\nsession id: " + httpSession.getId();
    }
}

Output

To try examples, run embedded Jetty (configured in pom.xml of example project below):

mvn jetty:run

Refreshing above page multiple times will increase the counter with same session id, until the session expires.

Integration Test

package com.logicbig.example;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.assertj.MockMvcTester; // New in Spring 6.2+ / 7
import org.springframework.web.context.WebApplicationContext;

import java.io.UnsupportedEncodingException;
import static org.assertj.core.api.Assertions.assertThat;

@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {MyWebConfig.class})
@WebAppConfiguration
public class MyControllerIntegrationTest {

    @Autowired
    private WebApplicationContext wac;

    private MockMvcTester mockMvc;

    @BeforeEach
    public void setup() {
        // MockMvcTester can be initialized from a WebApplicationContext
        this.mockMvc = MockMvcTester.from(this.wac);
    }

    @Test
    public void firstRequest_ShouldReturnCounterOne() {
        assertThat(mockMvc.get().uri("/").exchange())
                .hasStatusOk()
                .bodyText().contains("session counter : 1", "session id:");
    }

    @Test
    public void subsequentRequests_ShouldIncrementCounter() {
        MockHttpSession session = new MockHttpSession();

        // First request
        assertThat(mockMvc.get().uri("/").session(session).exchange())
                .hasStatusOk()
                .bodyText().contains("session counter : 1");

        // Second request
        assertThat(mockMvc.get().uri("/").session(session).exchange())
                .hasStatusOk()
                .bodyText().contains("session counter : 2");

        // Third request
        assertThat(mockMvc.get().uri("/").session(session).exchange())
                .hasStatusOk()
                .bodyText().contains("session counter : 3");
    }

    @Test
    public void differentSessions_ShouldHaveIndependentCounters() {
        MockHttpSession sessionA = new MockHttpSession();
        MockHttpSession sessionB = new MockHttpSession();

        assertThat(mockMvc.get().uri("/").session(sessionA).exchange())
                .bodyText().contains("session counter : 1");

        assertThat(mockMvc.get().uri("/").session(sessionA).exchange())
                .bodyText().contains("session counter : 2");

        assertThat(mockMvc.get().uri("/").session(sessionB).exchange())
                .bodyText().contains("session counter : 1");
    }

    @Test
    public void response_ShouldContainSessionId() {
        MockHttpSession session = new MockHttpSession();

        assertThat(mockMvc.get().uri("/").session(session).exchange())
                .hasStatusOk()
                .bodyText().contains("session id: " + session.getId());
    }

    @Test
    public void differentSessions_ShouldHaveDifferentSessionIds() {
        MockHttpSession sessionA = new MockHttpSession();
        MockHttpSession sessionB = new MockHttpSession();

        var resultA = mockMvc.get().uri("/").session(sessionA).exchange();
        var resultB = mockMvc.get().uri("/").session(sessionB).exchange();

        try {
            assertThat(resultA.getResponse().getContentAsString())
                    .isNotEqualTo(resultB.getResponse().getContentAsString());
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }
}
mvn clean test -Dtest=MyControllerIntegrationTest

Output

$ mvn clean test -Dtest=MyControllerIntegrationTest
[INFO] Scanning for projects...
[INFO]
[INFO] ----------< com.logicbig.example:http-session-param-example >-----------
[INFO] Building http-session-param-example 1.0-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ war ]---------------------------------
[INFO]
[INFO] --- clean:3.2.0:clean (default-clean) @ http-session-param-example ---
[INFO] Deleting D:\example-projects\spring-mvc\session-handling\http-session-param-example\target
[INFO]
[INFO] --- resources:3.3.1:resources (default-resources) @ http-session-param-example ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory D:\example-projects\spring-mvc\session-handling\http-session-param-example\src\main\resources
[INFO]
[INFO] --- compiler:3.15.0:compile (default-compile) @ http-session-param-example ---
[INFO] Recompiling the module because of changed source code.
[INFO] Compiling 3 source files with javac [debug target 25] to target\classes
[INFO]
[INFO] --- resources:3.3.1:testResources (default-testResources) @ http-session-param-example ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] skip non existing resourceDirectory D:\example-projects\spring-mvc\session-handling\http-session-param-example\src\test\resources
[INFO]
[INFO] --- compiler:3.15.0:testCompile (default-testCompile) @ http-session-param-example ---
[INFO] Recompiling the module because of changed dependency.
[INFO] Compiling 1 source file with javac [debug target 25] to target\test-classes
[INFO]
[INFO] --- surefire:3.2.5:test (default-test) @ http-session-param-example ---
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
[WARNING] file.encoding cannot be set as system property, use <argLine>-Dfile.encoding=...</argLine> instead
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.logicbig.example.MyControllerIntegrationTest
[INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.938 s -- in com.logicbig.example.MyControllerIntegrationTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 11.527 s
[INFO] Finished at: 2026-03-31T12:10:32+08:00
[INFO] ------------------------------------------------------------------------
INFO: Completed initialization in 2 ms
INFO: Completed initialization in 1 ms
INFO: Completed initialization in 2 ms
INFO: Completed initialization in 3 ms
INFO: Completed initialization in 4 ms

Example Project

Dependencies and Technologies Used:

  • spring-webmvc 7.0.6 (Spring Web MVC)
     Version Compatibility: 3.2.9.RELEASE - 7.0.6Version List
    ×

    Version compatibilities of spring-webmvc with this example:

      javax.servlet-api:3.x
    • 3.2.9.RELEASE
    • 3.2.10.RELEASE
    • 3.2.11.RELEASE
    • 3.2.12.RELEASE
    • 3.2.13.RELEASE
    • 3.2.14.RELEASE
    • 3.2.15.RELEASE
    • 3.2.16.RELEASE
    • 3.2.17.RELEASE
    • 3.2.18.RELEASE
    • 4.0.0.RELEASE
    • 4.0.1.RELEASE
    • 4.0.2.RELEASE
    • 4.0.3.RELEASE
    • 4.0.4.RELEASE
    • 4.0.5.RELEASE
    • 4.0.6.RELEASE
    • 4.0.7.RELEASE
    • 4.0.8.RELEASE
    • 4.0.9.RELEASE
    • 4.1.0.RELEASE
    • 4.1.1.RELEASE
    • 4.1.2.RELEASE
    • 4.1.3.RELEASE
    • 4.1.4.RELEASE
    • 4.1.5.RELEASE
    • 4.1.6.RELEASE
    • 4.1.7.RELEASE
    • 4.1.8.RELEASE
    • 4.1.9.RELEASE
    • 4.2.0.RELEASE
    • 4.2.1.RELEASE
    • 4.2.2.RELEASE
    • 4.2.3.RELEASE
    • 4.2.4.RELEASE
    • 4.2.5.RELEASE
    • 4.2.6.RELEASE
    • 4.2.7.RELEASE
    • 4.2.8.RELEASE
    • 4.2.9.RELEASE
    • 4.3.0.RELEASE
    • 4.3.1.RELEASE
    • 4.3.2.RELEASE
    • 4.3.3.RELEASE
    • 4.3.4.RELEASE
    • 4.3.5.RELEASE
    • 4.3.6.RELEASE
    • 4.3.7.RELEASE
    • 4.3.8.RELEASE
    • 4.3.9.RELEASE
    • 4.3.10.RELEASE
    • 4.3.11.RELEASE
    • 4.3.12.RELEASE
    • 4.3.13.RELEASE
    • 4.3.14.RELEASE
    • 4.3.15.RELEASE
    • 4.3.16.RELEASE
    • 4.3.17.RELEASE
    • 4.3.18.RELEASE
    • 4.3.19.RELEASE
    • 4.3.20.RELEASE
    • 4.3.21.RELEASE
    • 4.3.22.RELEASE
    • 4.3.23.RELEASE
    • 4.3.24.RELEASE
    • 4.3.25.RELEASE
    • 4.3.26.RELEASE
    • 4.3.27.RELEASE
    • 4.3.28.RELEASE
    • 4.3.29.RELEASE
    • 4.3.30.RELEASE
    • 5.0.0.RELEASE
    • 5.0.1.RELEASE
    • 5.0.2.RELEASE
    • 5.0.3.RELEASE
    • 5.0.4.RELEASE
    • 5.0.5.RELEASE
    • 5.0.6.RELEASE
    • 5.0.7.RELEASE
    • 5.0.8.RELEASE
    • 5.0.9.RELEASE
    • 5.0.10.RELEASE
    • 5.0.11.RELEASE
    • 5.0.12.RELEASE
    • 5.0.13.RELEASE
    • 5.0.14.RELEASE
    • 5.0.15.RELEASE
    • 5.0.16.RELEASE
    • 5.0.17.RELEASE
    • 5.0.18.RELEASE
    • 5.0.19.RELEASE
    • 5.0.20.RELEASE
    • 5.1.0.RELEASE
    • 5.1.1.RELEASE
    • 5.1.2.RELEASE
    • 5.1.3.RELEASE
    • 5.1.4.RELEASE
    • 5.1.5.RELEASE
    • 5.1.6.RELEASE
    • 5.1.7.RELEASE
    • 5.1.8.RELEASE
    • 5.1.9.RELEASE
    • 5.1.10.RELEASE
    • 5.1.11.RELEASE
    • 5.1.12.RELEASE
    • 5.1.13.RELEASE
    • 5.1.14.RELEASE
    • 5.1.15.RELEASE
    • 5.1.16.RELEASE
    • 5.1.17.RELEASE
    • 5.1.18.RELEASE
    • 5.1.19.RELEASE
    • 5.1.20.RELEASE
    • 5.2.0.RELEASE
    • 5.2.1.RELEASE
    • 5.2.2.RELEASE
    • 5.2.3.RELEASE
    • 5.2.4.RELEASE
    • 5.2.5.RELEASE
    • 5.2.6.RELEASE
    • 5.2.7.RELEASE
    • 5.2.8.RELEASE
    • 5.2.9.RELEASE
    • 5.2.10.RELEASE
    • 5.2.11.RELEASE
    • 5.2.12.RELEASE
    • 5.2.13.RELEASE
    • 5.2.14.RELEASE
    • 5.2.15.RELEASE
    • 5.2.16.RELEASE
    • 5.2.17.RELEASE
    • 5.2.18.RELEASE
    • 5.2.19.RELEASE
    • 5.2.20.RELEASE
    • 5.2.21.RELEASE
    • 5.2.22.RELEASE
    • 5.2.23.RELEASE
    • 5.2.24.RELEASE
    • 5.2.25.RELEASE
    • 5.3.0
    • 5.3.1
    • 5.3.2
    • 5.3.3
    • 5.3.4
    • javax.servlet-api:4.x
    • 5.3.5
    • 5.3.6
    • 5.3.7
    • 5.3.8
    • 5.3.9
    • 5.3.10
    • 5.3.11
    • 5.3.12
    • 5.3.13
    • 5.3.14
    • 5.3.15
    • 5.3.16
    • 5.3.17
    • 5.3.18
    • 5.3.19
    • 5.3.20
    • 5.3.21
    • 5.3.22
    • 5.3.23
    • 5.3.24
    • 5.3.25
    • 5.3.26
    • 5.3.27
    • 5.3.28
    • 5.3.29
    • 5.3.30
    • 5.3.31
    • 5.3.32
    • 5.3.33
    • 5.3.34
    • 5.3.35
    • 5.3.36
    • 5.3.37
    • 5.3.38
    • 5.3.39
    • javax.* -> jakarta.*
      jakarta.servlet-api:6.x
      Java 17 min
    • 6.0.0
    • 6.0.1
    • 6.0.2
    • 6.0.3
    • 6.0.4
    • 6.0.5
    • 6.0.6
    • 6.0.7
    • 6.0.8
    • 6.0.9
    • 6.0.10
    • 6.0.11
    • 6.0.12
    • 6.0.13
    • 6.0.14
    • 6.0.15
    • 6.0.16
    • 6.0.17
    • 6.0.18
    • 6.0.19
    • 6.0.20
    • 6.0.21
    • 6.0.22
    • 6.0.23
    • 6.1.0
    • 6.1.1
    • 6.1.2
    • 6.1.3
    • 6.1.4
    • 6.1.5
    • 6.1.6
    • 6.1.7
    • 6.1.8
    • 6.1.9
    • 6.1.10
    • 6.1.11
    • 6.1.12
    • 6.1.13
    • 6.1.14
    • 6.1.15
    • 6.1.16
    • 6.1.17
    • 6.1.18
    • 6.1.19
    • 6.1.20
    • 6.1.21
    • 6.2.0
    • 6.2.1
    • 6.2.2
    • 6.2.3
    • 6.2.4
    • 6.2.5
    • 6.2.6
    • 6.2.7
    • 6.2.8
    • 6.2.9
    • 6.2.10
    • 6.2.11
    • 6.2.12
    • 6.2.13
    • 6.2.14
    • 6.2.15
    • 6.2.16
    • 6.2.17
    • 7.0.0
    • 7.0.1
    • 7.0.2
    • 7.0.3
    • 7.0.4
    • 7.0.5
    • 7.0.6

    Versions in green have been tested.

  • spring-test 7.0.6 (Spring TestContext Framework)
  • junit-jupiter-engine 6.0.3 (Module "junit-jupiter-engine" of JUnit)
  • assertj-core 3.26.3 (Rich and fluent assertions for testing in Java)
  • jakarta.servlet-api 6.1.0 (Jakarta Servlet API documentation)
  • JDK 25
  • Maven 3.9.11

Spring MVC - Using HttpSession as handler arg Select All Download
  • http-session-param-example
    • src
      • main
        • java
          • com
            • logicbig
              • example
                • MyController.java
        • test
          • java
            • com
              • logicbig
                • example

    See Also

    Join