Spring Boot では、spring-boot-starter-session-data-redis を依存に加え、spring.data.redis.host で Redis の場所を指定するだけで、HttpSession の中身が Redis に保存されます。 コードの session.getAttribute() / setAttribute() はそのままで、アプリを何台に増やしてもログイン状態が共有されます。Spring Boot 4 では、Redis 用のプロパティが spring.session.redis.* から spring.session.data.redis.* に変わった点に注意します。

# application.yml(最小)
spring:
  data:
    redis:
      host: localhost
      port: 6379
  session:
    timeout: 30m
項目 既定値(ソースで確認)
Redis のキー spring:session:sessions:<セッションID>(Hash 型)
セッションの有効期限 30分(spring.session.timeout → 無ければ server.servlet.session.timeout)
値のシリアライズ Java の標準シリアライズ(属性のクラスは Serializable が必要)
Cookie の名前 SESSION(値はセッション ID を Base64 にしたもの)
Cookie の属性 HttpOnly・SameSite=Lax
保存のタイミング on-save(レスポンスを返すとき)

確認環境:Spring Boot 4.1.1・Spring Session 4.1.1・Spring Data Redis 4.1.1・Spring Security 7.1.1・Spring Framework 7.0.9 のソース(GitHub のタグ)を読み、既定値・キーの形・書き込みの順番を確かめました。そのうえで、ソースと同じコマンド(HMSET・PEXPIREAT・RENAME など)と同じシリアライズ処理を Java 21 から Valkey 7.2.13(Ubuntu 24.04、apt)に送り、保存される形を valkey-cli で確かめています。JSON の形は、ソースから作った Jackson 3.1.5 で Spring Data Redis と同じ型情報の付け方を再現して確かめました。Spring Boot の設定の説明は Spring Boot・Spring Session の公式ドキュメントによります(確認日 2026年10月13日)。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Spring Session Redisでセッション共有|設定と完成コード」

仕組み:HttpSession の保存先を Redis に替える

ふつうの Spring Boot アプリでは、セッションはアプリのメモリ(Tomcat の中)にあります。2台に増やすと、1台目でログインしても2台目は知らないので、ロードバランサーが2台目に振るとログアウトされたように見えます。

Spring Session は、サーブレットフィルター(SessionRepositoryFilter)で HttpSession を差し替え、中身を Redis に読み書きします。

段階 起きること
1. リクエストが来る フィルターが Cookie SESSION からセッション ID を取り出す
2. 読み込み Redis の spring:session:sessions:<ID> を読む
3. アプリの処理 session.getAttribute() / setAttribute() は今までどおり
4. レスポンス 変わった属性だけを Redis に書き、有効期限を延ばす

どのアプリも同じ Redis を見るので、どの台にリクエストが来ても同じセッションになります。 Cookie とセッションの基本は関連記事「Cookieとセッションの違いと仕組み|セッションIDの流れを図解」、Redis そのものの使い方は関連記事「Redisとは?使い方入門|データ型・キャッシュ・よくある失敗を実行結果で解説」で扱っています。

依存関係

Spring Boot 4.1.1 の BOM では、Spring Session は 4.1.1、Spring Data Redis は 4.1.1(Spring Data 2026.0.1)、Lettuce は 7.5.2.RELEASE になっています。

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>4.1.1</version>
</parent>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-webmvc</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-session-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-session-data-redis-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

Gradle の場合です。

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-webmvc'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.springframework.boot:spring-boot-starter-session-data-redis'
    testImplementation 'org.springframework.boot:spring-boot-starter-session-data-redis-test'
}

spring-boot-starter-session-data-redis の中身は、Boot 4.1.1 のソースでは spring-boot-starter-data-redis(Lettuce)と spring-boot-session-data-redis(自動設定)です。spring-session-data-redis を直接書く必要はなく、版の指定もいりません。 Boot 4 では spring-boot-starter-web は「deprecated in favor of spring-boot-starter-webmvc」とされているので、webmvc を使っています。Boot 3 からの移行全体は関連記事「Spring Boot 4移行の手順|3から4の変更点一覧とチェックリスト」で扱っています。

設定:application.yml の全体

spring:
  data:
    redis:
      host: ${REDIS_HOST:localhost}
      port: ${REDIS_PORT:6379}
      password: ${REDIS_PASSWORD:}
      # ssl:
      #   enabled: true        # マネージドの Redis で TLS を使うとき
  session:
    timeout: 30m               # 最後のアクセスから30分で切れる
    data:
      redis:
        namespace: spring:session:myapp   # アプリごとにキーを分ける
        flush-mode: on-save
        save-mode: on-set-attribute
        repository-type: default          # indexed にするとイベント・ユーザー別検索が使える

server:
  servlet:
    session:
      cookie:
        http-only: true
        secure: true           # HTTPS のときだけ送る
        same-site: lax

プロパティ一覧(Boot 4.1.1 のソースで確認した既定値)

プロパティ 既定値 意味
spring.session.timeout なし(下の値を使う) セッションの有効期限。単位なしは秒
server.servlet.session.timeout 30m 上が無いときに使われる
spring.session.data.redis.namespace spring:session キーの頭に付ける名前
spring.session.data.redis.flush-mode on-save いつ Redis に書くか。immediate は変更のたびに書く
spring.session.data.redis.save-mode on-set-attribute 何を書くか。on-get-attribute・always もある
spring.session.data.redis.repository-type default indexed にすると索引つきの保存方法になる
spring.session.data.redis.configure-action notify-keyspace-events indexed のとき、起動時に Redis の通知設定を変える
spring.session.data.redis.cleanup-cron 0 * * * * *(indexed のみ) 期限切れの掃除の間隔
spring.session.servlet.filter-order Integer.MIN_VALUE + 50 フィルターの順番
spring.session.servlet.filter-dispatcher-types async・error・request フィルターをかける種類

Boot 3 から名前が変わったプロパティ

Boot 4.1.1 のメタデータでは、次の6つが「since 4.0.0」「level: error」の非推奨(=もう使えない)とされ、置き換え先が示されています。

Boot 3 まで Boot 4
spring.session.redis.namespace spring.session.data.redis.namespace
spring.session.redis.flush-mode spring.session.data.redis.flush-mode
spring.session.redis.save-mode spring.session.data.redis.save-mode
spring.session.redis.repository-type spring.session.data.redis.repository-type
spring.session.redis.configure-action spring.session.data.redis.configure-action
spring.session.redis.cleanup-cron spring.session.data.redis.cleanup-cron

Boot 3 までの名前のまま書いても起動時のエラーにはならず、設定が効かないだけです。 たとえば namespace が変わらず、ほかのアプリとキーがぶつかります。Spring Session のリファレンスの Redis のページには spring.session.redis.namespace の例が残っているので、Boot 4 では読み替えます。なお、Boot 2 時代の記事にある spring.session.store-type=redis は、Boot 4.1.1 のセッション関連のソースとメタデータには見当たらず、依存を入れるだけで有効になります。

完成コード:ログインとセッションの読み書き

ログインすると、訪問回数と買い物かごをセッションに入れる小さなアプリです。

// SecurityConfig.java
package com.example.session;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class SecurityConfig {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
            .formLogin(Customizer.withDefaults())
            .logout(Customizer.withDefaults());
        return http.build();
    }

    @Bean
    UserDetailsService users() {
        // 動作確認用。本番は DB などから読む
        return new InMemoryUserDetailsManager(
            User.withUsername("tanaka").password("{noop}password").roles("USER").build());
    }
}
// CartItem.java:セッションに入れるクラスは Serializable にする
package com.example.session;

import java.io.Serializable;

public record CartItem(String sku, int quantity) implements Serializable {
}
// SessionController.java
package com.example.session;

import jakarta.servlet.http.HttpSession;
import java.security.Principal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.springframework.web.bind.annotation.*;

@RestController
public class SessionController {

    @GetMapping("/me")
    public Map<String, Object> me(HttpSession session, Principal principal) {
        Integer visits = (Integer) session.getAttribute("visits");
        visits = (visits == null) ? 1 : visits + 1;
        session.setAttribute("visits", visits);   // 変えたら setAttribute し直す
        return Map.of(
            "user", principal.getName(),
            "sessionId", session.getId(),
            "visits", visits,
            "server", System.getenv().getOrDefault("HOSTNAME", "local"));
    }

    @PostMapping("/cart")
    public List<CartItem> add(@RequestParam String sku, HttpSession session) {
        @SuppressWarnings("unchecked")
        List<CartItem> cart = (List<CartItem>) session.getAttribute("cart");
        cart = (cart == null) ? new ArrayList<>() : new ArrayList<>(cart);
        cart.add(new CartItem(sku, 1));
        session.setAttribute("cart", cart);       // 中身を変えたリストを入れ直す
        return cart;
    }
}

取り出したオブジェクトの中身を変えたら、setAttribute で入れ直します。 既定の save-mode: on-set-attribute では、setAttribute された属性だけが Redis に書かれるためです。取り出したリストに add しただけでは、Redis 側は前の内容のままになります(save-mode: always にすると毎回すべて書きますが、通信量が増えます)。

2台で共有されることを確かめる手順

# Redis(または Valkey)を起動
valkey-server --port 6379 --daemonize yes

# 同じアプリを2つのポートで起動
java -jar app.jar --server.port=8080 &
java -jar app.jar --server.port=8081 &

# 8080 でログインし、Cookie を保存
curl -c cookie.txt -b cookie.txt -X POST \
  -d 'username=tanaka&password=password' http://localhost:8080/login

# 同じ Cookie で 8081 に行く → ログイン済みとして応答する
curl -b cookie.txt http://localhost:8081/me

フォームログインでは CSRF トークンが必要なので、curl で試すときは先にログイン画面を取得してトークンを付けるか、確認用に CSRF の設定を見直します。SecurityFilterChain の書き方とデータベース認証は関連記事「Spring Security入門|SecurityFilterChainとDB認証」で、CSRF と SameSite は関連記事「CSRF対策とSameSite|Cookie送信をChromeで実測し多層防御を実装」で扱っています。

Redis に保存されるキーの形

既定(repository-type: default)

Spring Session 4.1.1 の RedisSessionRepository は、1つのセッションを1つの Hashに保存します。ソースでは、キーは namespace + ":sessions:" + セッションID、項目名は creationTime・lastAccessedTime・maxInactiveInterval・sessionAttr:<属性名> です。書き込みは「変わった項目を HMSET」→「有効期限を PEXPIREAT(最後のアクセス+有効期限)」の順です。

この順番と同じ処理(値は Spring の DefaultSerializer と同じ ObjectOutputStream で変換)を Valkey に送り、valkey-cli で見た結果です。

valkey-cli --scan --pattern 'spring:session:*'
valkey-cli type  spring:session:sessions:6b8a5400-a5de-43a3-82db-7675243955fa
valkey-cli hkeys spring:session:sessions:6b8a5400-a5de-43a3-82db-7675243955fa
valkey-cli ttl   spring:session:sessions:6b8a5400-a5de-43a3-82db-7675243955fa
spring:session:sessions:6b8a5400-a5de-43a3-82db-7675243955fa
hash
creationTime
sessionAttr:loginUser
lastAccessedTime
maxInactiveInterval
1794
項目 中身 サイズ(JDK シリアライズ)
creationTime 作成時刻(ミリ秒の Long) 82 バイト
lastAccessedTime 最後のアクセス時刻(Long) 82 バイト
maxInactiveInterval 有効期限の秒数(Integer、1800) 81 バイト
sessionAttr:loginUser アプリが入れた属性 201 バイト(3項目のレコード)

値はバイナリ(Java のシリアライズ形式)で、valkey-cli では読みにくい形です。 --no-raw で見ると、先頭が Java のシリアライズの印 \xac\xed\x00\x05 になっています。

"\xac\xed\x00\x05sr\x00\x11java.lang.Integer\x12\xe2\xa0\xa4\xf7\x81\x878\x02\x00\x01I\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\a\b"

読みにくいだけで、文字列は中に残っています。 属性に入れた名前「田中 一郎」は、hget の出力にそのまま見えました。Redis にパスワードをかけ、アクセスできる範囲を絞るのはこのためです。

ログインでセッション ID が変わる(RENAME)

Spring Security は、ログインの瞬間にセッション ID を変えます(セッション固定攻撃への対策)。RedisSessionRepository のソースでは、ID が変わると RENAME 元のキー 新しいキー で Hash ごと移します。今回の再現でも、未ログインのときに作った Hash が、ログイン後の ID のキーに移り、そこに属性が足されました。ログイン前後で SESSION Cookie の値が変わるのは正常な動きです。

Cookie の値は、セッション ID を Base64 にしたものです(DefaultCookieSerializer の useBase64Encoding の既定が true)。再現プログラムで、DefaultCookieSerializer の既定値(名前 SESSION・HttpOnly・SameSite=Lax)に沿って組み立てた例です。

Set-Cookie: SESSION=NmI4YTU0MDAtYTVkZS00M2EzLTgyZGItNzY3NTI0Mzk1NWZh; Path=/; HttpOnly; SameSite=Lax
session id = 6b8a5400-a5de-43a3-82db-7675243955fa

NmI4YTU0... を Base64 から戻すと、6b8a5400-... になります。Redis のキーを探すときは、Cookie の値を base64 -d してから探します。

indexed(repository-type: indexed)

indexed にすると、RedisIndexedSessionRepository が使われ、キーが4種類になります。ソースの書き込み順で再現した結果です。

spring:session:expirations:1791873600000  type=set
spring:session:index:org.springframework.session.FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME:tanaka  type=set ttl=-1
spring:session:sessions:1294ff6e-86af-45ac-b537-e970078e2ff2  type=hash ttl=2100
spring:session:sessions:expires:1294ff6e-86af-45ac-b537-e970078e2ff2  type=string ttl=1800
キー 型 役割
sessions:<ID> Hash セッション本体。有効期限+5分(2100秒)で消える
sessions:expires:<ID> String(空) 期限切れを知るための「影のキー」。有効期限(1800秒)で消える
expirations:<分単位の時刻> Set その分に期限が来るセッションの一覧。掃除用
index:...PRINCIPAL_NAME_INDEX_NAME:<ユーザー名> Set ユーザー名 → セッション ID の索引

影のキーが期限で消えると、Redis が「expired」の通知を出し、Spring Session が SessionExpiredEvent を出します。 そのため indexed では、起動時に Redis の notify-keyspace-events に Egx を足す処理が動きます(ConfigureNotifyKeyspaceEventsAction)。Valkey で config set notify-keyspace-events Egx にして影のキーを2秒で切らすと、次の通知が届きました。

pmessage
__keyevent@0__:*
__keyevent@0__:expired
spring:session:sessions:expires:demo
比べる点 default indexed
キーの数(1セッション) 1 4種類(索引・掃除用を含む)
ユーザー名でセッションを探す(FindByIndexNameSessionRepository) × ○(同時ログインの制御などに使う)
SessionCreatedEvent・SessionExpiredEvent など × ○
Redis の設定変更(CONFIG SET) 不要 起動時に行う(configure-action: none で止められる)
Redis Cluster 問題なし ドキュメントでは、通知を1つのノードからしか受けない注意がある

イベントやユーザー別の検索が要らなければ、default のままが単純です。 Spring Session のドキュメントも、2つの違いをこの観点で説明しています。

セッションの有効期限

Boot 4.1.1 の SessionAutoConfiguration では、有効期限は次の順に決まります。

  1. spring.session.timeout
  2. それが無ければ server.servlet.session.timeout(既定 30分)
  3. WAR として外部の Tomcat に置いたときは spring.session.timeout だけを見る

有効期限は「最後のアクセスから」数えます。 既定の RedisSessionRepository では、保存のたびに PEXPIREAT で「最後のアクセス時刻+有効期限」を設定し直すので、使い続ける限り切れません。再現では、保存直後の残り時間(PTTL)が 1,799,685 ミリ秒でした。

確かめたいこと コマンド
残り時間(秒) valkey-cli ttl spring:session:sessions:<ID>
残り時間(ミリ秒) valkey-cli pttl spring:session:sessions:<ID>
セッションの数 valkey-cli --scan --pattern 'spring:session:sessions:*' | wc -l(indexed では expires: も数に入る点に注意)
特定のセッションを消す(強制ログアウト) valkey-cli del spring:session:sessions:<ID>

本番の Redis では、全件を一度に返す KEYS * ではなく --scan(SCAN コマンド)を使います。

コードからセッションごとに変える場合は session.setMaxInactiveInterval(秒) を使います。保存される maxInactiveInterval の値も変わります。

シリアライズ:JDK と JSON

JDK シリアライズ(既定)

Spring Session 4.1.1 の AbstractRedisHttpSessionConfiguration は、キーと Hash の項目名を文字列で、値を springSessionDefaultRedisSerializer という名前の Bean(無ければ RedisTemplate の既定=JDK シリアライズ)で変換します。

長所 短所
設定なしで何でも入る(Serializable なら) 属性のクラスに Serializable が必要
Spring Security の認証情報もそのまま入る クラスを変えると、保存済みのセッションが読めなくなることがある
値が読みにくい。Java 以外から読めない

JSON にする

Spring Session のドキュメントでは、springSessionDefaultRedisSerializer という名前で RedisSerializer の Bean を定義すると、JSON に替えられると説明されています。Spring Security を使うときは、Security の Jackson モジュールを登録します(ドキュメントのコードを Boot 4 の Jackson 3 に合わせた形)。

package com.example.session;

import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.serializer.JacksonJsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.security.jackson.SecurityJacksonModules;
import tools.jackson.databind.json.JsonMapper;
import tools.jackson.databind.jsontype.BasicPolymorphicTypeValidator;

@Configuration
public class SessionConfig implements BeanClassLoaderAware {

    private ClassLoader loader;

    // Bean の名前は springSessionDefaultRedisSerializer にする(名前で置き換わる)
    @Bean
    public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
        // 自分のクラス(CartItem など)も、型として許可しておく
        BasicPolymorphicTypeValidator.Builder allow = BasicPolymorphicTypeValidator.builder()
                .allowIfSubType("com.example.session.");
        JsonMapper mapper = JsonMapper.builder()
                .addModules(SecurityJacksonModules.getModules(this.loader, allow))
                .build();
        return new JacksonJsonRedisSerializer<>(mapper, Object.class);
    }

    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        this.loader = classLoader;
    }
}

SecurityJacksonModules(Spring Security 7.0 から)の Javadoc では、Security の Jackson モジュールを集め、型情報の自動付与と、クラス名を確かめる PolymorphicTypeValidator を設定すると説明されています。getModules(loader, builder) で自分のクラスを許可に足せます。Spring Security を使わない場合は、Spring Data Redis の GenericJacksonJsonRedisSerializer.builder().enableDefaultTyping(検証器).build() で同じように作れます(GenericJackson2JsonRedisSerializer は Spring Data Redis 4.0 で非推奨)。

JSON にすると何が変わるか

Spring Data Redis の JSON シリアライザーと同じ型情報の付け方(クラス名を @class に入れる)で、同じセッションを保存しました。

creationTime => 1791871725628
maxInactiveInterval => 1800
sessionAttr:loginUser => {"@class":"demo.LoginUser","username":"tanaka","displayName":"田中 一郎","roles":["java.util.ImmutableCollections$List12",["ROLE_USER"]]}
項目 JDK JSON
creationTime のサイズ 82 バイト 13 バイト
maxInactiveInterval のサイズ 81 バイト 4 バイト
sessionAttr:loginUser のサイズ 201 バイト 141 バイト
valkey-cli で読めるか 読みにくい そのまま読める
Serializable が必要か 必要 不要(JSON にできるクラスであること)
クラスにフィールドを足したとき serialVersionUID が変わると読めない 既定では知らない項目を無視して読める
気をつける点 クラスの版をそろえる @class に入るクラスを許可リストで絞る

JSON にはクラス名がそのまま入ります。 List.of(...) で作ったリストは、JDK 内部のクラス名 java.util.ImmutableCollections$List12 として保存されました。読み戻すときはこのクラス名でオブジェクトを作るため、許可していないクラス名は読み込みで拒否されます。 許可リストから自分のパッケージを外して読むと、次の例外になりました。

tools.jackson.databind.exc.InvalidTypeIdException: Could not resolve type id 'demo.LoginUser' as a subtype of `java.lang.Object`: Configured `PolymorphicTypeValidator` (of type `tools.jackson.databind.jsontype.BasicPolymorphicTypeValidator`) denied resolution

Spring Data Redis の RedisSerializer.json() は、ソースでは enableUnsafeDefaultTyping()(どのクラスでも作れる設定)になっています。Redis に書き込める人が任意のクラスを作らせられる形になるため、セッションのように外から影響を受けうるデータには、許可リストを付けた設定を使います。Jackson の基本的な使い方は関連記事「Jackson ObjectMapperの使い方|JSON変換・日付・record」で扱っています。

ログイン状態が共有されるしくみ

Spring Security は、ログインした結果(SecurityContext)を、セッションの SPRING_SECURITY_CONTEXT という属性に保存します。Spring Session を入れると、この属性も Redis の sessionAttr:SPRING_SECURITY_CONTEXT に入ります。

段階 Redis で起きること
ログイン画面を開く 未ログインのセッションの Hash ができる
ログイン成功 RENAME で ID が変わり、sessionAttr:SPRING_SECURITY_CONTEXT が書かれる
別の台にリクエスト 同じ Hash を読み、ログイン済みとして扱われる
ログアウト セッションが無効になり、Hash が消える
何もしないで30分 Hash の有効期限が切れて消える

ユーザー情報のクラス(独自の UserDetails など)も、JDK シリアライズなら Serializable、JSON なら許可リストに入っている必要があります。 ログインは成功するのに次の画面でエラーになるときは、ここを疑います。

よくあるエラーと原因

Serializable でないオブジェクトを入れた

Spring の DefaultSerializer は、値が Serializable でないと例外を投げます。同じ処理で再現した結果です。

java.lang.IllegalArgumentException: DefaultSerializer requires a Serializable payload but received an object of type [demo.Cart]

ソースでは、これが SerializationFailedException(Failed to serialize object using DefaultSerializer)に包まれ、さらに Spring Data Redis の SerializationException(Cannot serialize)に包まれて出ます。Serializable なクラスの中に、Serializable でないフィールドがある場合は、次の形になりました。

java.io.NotSerializableException: demo.Cart

クラスを変えたら保存済みのセッションが読めない

serialVersionUID を書いていないクラスにフィールドを足し、変更前の形で保存したものを変更後のクラスで読みました。

java.io.InvalidClassException: demo.UserProfile; local class incompatible: stream classdesc serialVersionUID = -2108372345059697884, local class serialVersionUID = -5552006662998189354

ローリングアップデートで新旧のアプリが同じ Redis を見ると、この形で失敗します。 serialVersionUID を明示するか、JSON に替えるか、リリース時にセッションを切る(ログインし直してもらう)かを決めておきます。serialVersionUID の意味は関連記事「Java Serializableとは|serialVersionUIDの意味と手順」で扱っています。

一覧表

症状・メッセージ 原因 直し方
DefaultSerializer requires a Serializable payload 属性のクラスが Serializable でない implements Serializable を付ける
NotSerializableException: <クラス> 属性の中のフィールドが Serializable でない フィールドのクラスにも付ける・transient にする
InvalidClassException ... serialVersionUID 保存したときとクラスが変わった serialVersionUID を固定・JSON にする
Failed to deserialize payload 読むアプリにそのクラスが無い(別アプリと Redis を共有) namespace をアプリごとに分ける
InvalidTypeIdException ... denied resolution JSON の許可リストにクラスが無い 許可リストにパッケージを足す
Unable to configure Redis to keyspace notifications indexed で、Redis が CONFIG を許していない(マネージドの Redis など) Redis 側で通知を設定し、configure-action: none
Cron-based cleanup is only supported when spring.session.data.redis.repository-type is set to indexed. default で cleanup-cron を書いた indexed にするか、設定を消す
namespace などが効かない Boot 3 までの spring.session.redis.* のまま spring.session.data.redis.* に直す
2台目でログアウトされる Cookie SESSION が届いていない(ドメイン・Secure・パス) server.servlet.session.cookie.* を確かめる
リストに足したのに反映されない 取り出したオブジェクトを変えただけ setAttribute で入れ直す
Unable to connect to Redis 接続先・パスワード・TLS の違い spring.data.redis.* を確かめる

Unable to configure Redis to keyspace notifications と Cron-based cleanup ... の文面は、Spring Session 4.1.1 と Spring Boot 4.1.1 のソースから引いています。マネージドの Redis(ElastiCache など)の作り方は関連記事「ElastiCacheの使い方|Valkey/Redisの作り方と接続コード」で扱っています。

よくある質問

Q. Redis が止まったらどうなりますか。 A. セッションが読めないので、リクエストはエラーになります。セッションの保存先が1か所になるため、Redis 側の冗長化(レプリカ・自動フェイルオーバー)を用意します。

Q. スティッキーセッション(同じ台に振り続ける設定)との違いは何ですか。 A. スティッキーセッションは、ロードバランサーが同じ利用者を同じ台に送り続ける方法で、台が落ちるとその台のセッションは失われます。Spring Session はセッションを Redis に置くので、どの台が落ちてもセッションは残ります。

Q. Cookie の名前を JSESSIONID に戻したいです。 A. Boot 4.1.1 の自動設定では、server.servlet.session.cookie.name が Spring Session の Cookie 名に使われます。指定しなければ SESSION です。

Q. ログイン中のユーザーの一覧や、同時ログインの制限をしたいです。 A. ユーザー名からセッションを探す機能(FindByIndexNameSessionRepository)は、repository-type: indexed のときに使えます。

Q. テストはどう書きますか。 A. Boot 4.1.1 には、spring-boot-starter-session-data-redis とテスト用スターターをまとめた spring-boot-starter-session-data-redis-test があります。本物の Redis を使う結合テストは、Testcontainers で Redis のコンテナを立てる形が使えます。考え方は関連記事「Testcontainersの使い方|Spring BootでPostgreSQL」で扱っています。

まとめ

  • 依存に spring-boot-starter-session-data-redis、接続先を spring.data.redis.* に書くと、HttpSession が Redis に保存される
  • Boot 4 では spring.session.redis.* → spring.session.data.redis.* に変わった。Boot 3 までの名前は効かない
  • 既定のキーは spring:session:sessions:<ID> の Hash 1つ。項目は creationTime・lastAccessedTime・maxInactiveInterval・sessionAttr:<名前>
  • 有効期限は 最後のアクセス+30分(spring.session.timeout → server.servlet.session.timeout)。保存のたびに延びる
  • ログイン時は RENAME で ID が変わる。Cookie SESSION の値は ID の Base64
  • 既定の JDK シリアライズでは 属性に Serializable が必要。JSON にするなら 許可リストつきで
  • indexed は イベントとユーザー別検索が使えるが、キーが4種類になり、Redis の通知設定が必要

参考・出典

確認日はいずれも 2026年10月13日です。

  • Spring Boot Reference「Spring Session」:https://docs.spring.io/spring-boot/reference/web/spring-session.html
  • Spring Session Reference「Redis Configurations」:https://docs.spring.io/spring-session/reference/configuration/redis.html
  • Spring Boot(GitHub、v4.1.1):https://github.com/spring-projects/spring-boot/tree/v4.1.1
  • Spring Session(GitHub、4.1.1):https://github.com/spring-projects/spring-session/tree/4.1.1
  • Spring Data Redis(GitHub、4.1.1):https://github.com/spring-projects/spring-data-redis/tree/4.1.1
  • Spring Security(GitHub、7.1.1):https://github.com/spring-projects/spring-security/tree/7.1.1
  • Valkey Documentation「Keyspace notifications」:https://valkey.io/topics/notifications/

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Spring Session Redisでセッション共有|設定と完成コード」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?