Spring Session Hazelcast

What You’ll Learn

In this tutorial, you will deploy two Spring Boot applications that use Hazelcast as a HTTP session store and hence share sessions among each other.

Spring Session manages users' session information and supports clustered sessions rather than an application container-specific solution only. That is, more than one application instance can use the same store for user sessions. You can find more details in the Spring Session Documentation.

Before you Begin

  • JDK 1.8+

  • Apache Maven 3.2+

Enable HazelcastHttpSession

Before using Hazelcast session repository, let’s add the required dependencies:

pom.xml
<dependency>
    <groupId>com.hazelcast</groupId>
    <artifactId>hazelcast</artifactId>
    <version>${hazelcast.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-core</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-hazelcast</artifactId>
</dependency>

Now configure the app to use Hazelcast as HTTP session store:

SessionConfiguration.java
@Configuration
@EnableHazelcastHttpSession
class SessionConfiguration {
    // ...
}

Alternatively, you may extend HazelcastHttpSessionConfiguration to have more advanced configurations rather than using annotation based configuration:

@Configuration
class SessionConfiguration extends HazelcastHttpSessionConfiguration {
    // ...
}

Create a Hazelcast Instance Bean

After enabling Hazelcast HTTP Session, you need to provide a Hazelcast instance to be used by the session repository. This instance can be either a Hazelcast client or an embedded Hazelcast instance. You can see the details of client and embedded modes here on the documentation.

Notice some Hazelcast4 prefixes in the class names. If you use an older Hazelcast version (3.x), you need to drop these "4" s. For instance, use HazelcastIndexedSessionRepository instead of Hazelcast4IndexedSessionRepository:

  • Embedded

  • Client

SessionConfiguration.java
@Bean
@SpringSessionHazelcastInstance
public HazelcastInstance hazelcastInstance() {
    Config config = new Config();
    config.setClusterName("spring-session-cluster");

    // Add this attribute to be able to query sessions by their PRINCIPAL_NAME_ATTRIBUTE's
    AttributeConfig attributeConfig = new AttributeConfig()
            .setName(Hazelcast4IndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE)
            .setExtractorClassName(Hazelcast4PrincipalNameExtractor.class.getName());

    // Configure the sessions map
    config.getMapConfig(SESSIONS_MAP_NAME)
            .addAttributeConfig(attributeConfig).addIndexConfig(
            new IndexConfig(IndexType.HASH, Hazelcast4IndexedSessionRepository.PRINCIPAL_NAME_ATTRIBUTE));

    // Use custom serializer to de/serialize sessions faster. This is optional.
    SerializerConfig serializerConfig = new SerializerConfig();
    serializerConfig.setImplementation(new HazelcastSessionSerializer()).setTypeClass(MapSession.class);
    config.getSerializationConfig().addSerializerConfig(serializerConfig);

    return Hazelcast.newHazelcastInstance(config);
}
SessionConfiguration.java
@Bean
@SpringSessionHazelcastInstance
public HazelcastInstance hazelcastInstance() {
    ClientConfig clientConfig = new ClientConfig();

    // Configure cluster member addresses to be connected.
    clientConfig.getNetworkConfig().addAddress("127.0.0.1:5701");

    // If spring-session packages do not present in Hazelcast member's classpath,
    // these classes need to be deployed over the client. This is required since
    // Hazelcast updates sessions via entry processors.
    clientConfig.getUserCodeDeploymentConfig().setEnabled(true).addClass(Session.class)
            .addClass(MapSession.class).addClass(Hazelcast4SessionUpdateEntryProcessor.class);

    return HazelcastClient.newHazelcastClient(clientConfig);
}
You need to have a running Hazelcast cluster before starting a client. Also, to use all session repository functionalities (e.g. findByPrincipalName), Hazelcast members in a cluster must have the map configuration shown in the Embedded tab before they start. See the next section for starting a Hazelcast cluster.
You need to enable user code deployment in member configurations as well:
<user-code-deployment enabled="true">
    <class-cache-mode>ETERNAL</class-cache-mode>
    <provider-mode>LOCAL_AND_CACHED_CLASSES</provider-mode>
</user-code-deployment>

Start a Hazelcast Cluster

If you use Hazelcast client, you need to have a running cluster first. You can start Hazelcast members with one of the following methods:

  • Hazelcast Cloud

  • Docker Image

  • Hazelcast CLI

  • Download Packages

You can easily create a Hazelcast cluster on Hazelcast Cloud with just a few clicks. See Getting Started documentation for details.

You can start members inside Docker containers. See the documentation for details.

$ docker run hazelcast/hazelcast:$HAZELCAST_VERSION

You can start members via Hazelcast CLI. See the documentation for the installation instructions and details.

$ hz start

You can start members via start script in IMDG bundle.

$ sh bin/start.sh

You can find other ways of starting Hazelcast members and forming a cluster here.

Using an Existing Instance

Let’s say you already have a Hazelcast instance created elsewhere in your application. You can configure Spring Session to use that instance instead of creating a new one:

  • Embedded

  • Client

@Bean
@SpringSessionHazelcastInstance
public HazelcastInstance hazelcastInstance() {
    return Hazelcast.getHazelcastInstanceByName("existing-instance-name");
}
@Bean
@SpringSessionHazelcastInstance
public HazelcastInstance hazelcastInstance() {
    return HazelcastClient.getHazelcastClientByName("existing-client-name");
}

Customize the Session Repository

Now that you have provided a Hazelcast instance, let’s configure the session repository further. Note that these configurations are optional and they will fall back to the defaults when not provided:

  • Annotation

  • Config Extension

@Configuration
@EnableHazelcastHttpSession
class SessionConfiguration {

    @Bean
    public SessionRepositoryCustomizer<Hazelcast4IndexedSessionRepository> customize() {
        return (sessionRepository) -> {
            sessionRepository.setFlushMode(FlushMode.IMMEDIATE);
            sessionRepository.setSaveMode(SaveMode.ALWAYS);
            sessionRepository.setSessionMapName(SESSIONS_MAP_NAME);
            sessionRepository.setDefaultMaxInactiveInterval(900);
        };
    }

    // ...
}
@Configuration
class SessionConfiguration extends HazelcastHttpSessionConfiguration {

    SessionConfiguration() {
        setFlushMode(FlushMode.IMMEDIATE);
        setSaveMode(SaveMode.ALWAYS);
        setSessionMapName(SESSIONS_MAP_NAME);
        setMaxInactiveIntervalInSeconds(900);
    }

    // ...
}

Interactions with User Sessions

Once you completed the configurations, you can reach to the session repository by autowiring it:

@Autowired
Hazelcast4IndexedSessionRepository sessionRepository;

Although you do not need to reach this repository explicitly to store or load sessions, some of the methods might be needed such as findByIndexNameAndIndexValue or findByPrincipalName. Other than these, the following snippet will load and store the sessions for you:

@GetMapping("/check")
public String check(HttpServletRequest request) {
    HttpSession session = request.getSession(false);
    if (session == null) {
        return "No session found.";
    } else {
        session.setAttribute("attr1", 1);
        session.setMaxInactiveInterval(300);
        return "Session found: " + session.getId();
    }
}

And we are ready to test Hazelcast session store now!

Create the Endpoints

To test our session store behavior, create a few endpoints.

In SessionController.java you can find 3 of them all of which are GET mappings for simplicity:

  • /create: If there is no session associated with the request, creates a new one with the principal attribute sent as the request parameter.

  • /info: Gives the session details if the request has a session.

  • /list: Lists all the sessions with the same principalName of the request’s session.

Start the Applications

Let’s now start two application instances to verify that both use the same session store:

SERVER_PORT=8080 mvn spring-boot:run
SERVER_PORT=8081 mvn spring-boot:run

Test the Applications

  • Create a new session on the port 8080:

ss1
  • See session info on the port 8081:

ss2
  • Now create another session with the same principal. But be aware that you need to use a different browser or use the private mode of your browser to use separate cookies:

ss3
  • Let’s list all the sessions with the principal hazelcast. You can imagine this scenario - for example, finding all the active sessions of a user:

ss4

See Also

You can check other session replication tutorials that serve the same purpose but with different integrations: