- Spring
Spring Session Redisでセッション共有|設定と完成コード
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 では、有効期限は次の順に決まります。
spring.session.timeout- それが無ければ
server.servlet.session.timeout(既定 30分) - 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 が変わる。CookieSESSIONの値は 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期連続の黒字決算。