티스토리 뷰
spring data redis 4.1.0 버전에서 StackOverFlow 가 발생한다면(with redisson)
LichKing 2026. 7. 25. 18:09spring boot 4.1.0 이 릴리즈 되어서 최신 버전으로 업그레이드를 했다. 컴파일도 잘되고 문제가 없다고 생각했지만 redis 를 사용하는 부분에서 stack over flow 가 발생했다.

참고로 redis client 로는 redisson 을 사용하고 있었다. 간략한 예제 테스트 코드는 아래와 같다.
// 의존성
dependencies {
implementation("org.springframework.boot:spring-boot-starter-data-redis:4.1.0") {
exclude("io.lettuce", "lettuce-core")
}
implementation("org.redisson:redisson-spring-boot-starter:4.1.0")
testImplementation("org.springframework.boot:spring-boot-starter-test:4.1.0")
}
// 테스트
@SpringBootTest
class RedisTest {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Test
void redis_set_test() {
stringRedisTemplate.opsForValue().set("key", "value", Duration.ofSeconds(10));
String value = stringRedisTemplate.opsForValue().get("key");
assertThat(value).isEqualTo("value");
}
}
spring 자동구성에 통합하여 사용하기 위해 redisson-spring-boot-starter 를 추가하여 사용하고 있었고, 테스트 코드에서 set 호출시 stack over flow 가 발생하게 된다.
해결법은 간단하다. redisson-spring-boot-starter 의 버전을 4.6.0 이상으로 변경해주면 된다.(현재 최신버전은 4.6.1) 4.6.0 이상이 spring boot 4.1.x 에 대응하기 때문이다. 이 아래로는 왜 stack over flow 가 발생했는지를 찾아본 내용이다.
# 원인
먼저 set 을 호출하여 레디스에 write 작업을 실행하면 내부에선 DefaultValueOperations 객체의 set 메서드를 실행하게 된다. 그리고 이 내부구현이 조금 변경되었다.


위 메서드가 구현되어 있는 DefaultValueOperations 는 spring data redis 에서 제공하는 객체이다. 람다를 사용해 connection 의 set 을 호출하고 있는데, 잘보면 호출하는 connection.set 메서드의 호출부분이 미묘하게 다른걸 볼 수 있다. connection 은 RedisConnection 인터페이스이며, spring data redis 가 인터페이스를 제공하고 각 구현체들이 구현한다. redisson 은 RedissonConnection 을 구현체로 제공하고 있다.
4.1 에서 호출하고 있는 set 메서드는 spring data redis 4.1 에서 신규 추가된 메서드이다.

26년 3월에 추가된 메서드인데 처음부터 Deprecated 이다. 해당 메서드는 하위호환을 유지하며 변경하기위한 과도기 메서드인걸로 보인다. 인터페이스에 추가된 메서드이지만 추상 메서드가 아니라 구현 메서드로 되어있다. 이부분이 중요하다.
RedisConnection 의 계층 구조가 복잡한데, stringCommands 메서드도 계층 구조의 속하는 인터페이스가 제공하는 추상 메서드이다. 각 벤더의 RedisConnection 구현체는 stringCommands 도 구현해야한다.

구현을 보면 return this 로 스스로를 반환하고 있다. 이제 다시 위에서 본 인터페이스의 구현 메서드를 살펴보자.

stringCommands 는 자기 자신을 반환하는데 똑같은 set 메서드를 호출하니 재귀에 빠져서 계속 지금 보는 default 메서드를 호출하는 것이었다. 그렇다면 redisson-spring-boot-starter 버전을 올리면 왜 해결될까?

redisson 에서 spring data redis 에 신규 추가된 메서드를 오버라이딩하고 있기 때문에 재귀에 빠지지 않게 된다.
만약 spring data redis 에서 신규 메서드를 구현 메서드가 아닌 추상 메서드로 추가했다면 구버전 redisson 의 구현체에서 해당 메서드를 구현하지 않게 되기 때문에 재귀가 아니라 다른 형식의 예외를 일으켰을 것이다. 개인적으론 구현 메서드가 없다는 예외가 발생했다면 문제를 파악하기에 더 빨랐을거라고 생각한다.
구버전의 인터페이스를 구현하고 있는 구현체의 하위 호환을 유지하면서 인터페이스를 확장할때 default method 를 사용하면 이미 컴파일되어 있는 구버전의 구현체도 정상적으로 동작하게 된다. 이를 바이너리 호환이라고 한다.
이번에 발생한 재귀 문제는 구현 메서드로 추가한 spring data redis 의 인터페이스 확장과 자기 자신을 반환하도록 구현되어있는 redisson 의 구현이 일으킨 콜라보레이션이다. redisson 외에 대중적으로 많이 사용하는 lettuce, jedis 는 알아보지 않았으나 RedisConnection 인터페이스의 구현을 spring data redis 가 직접 제공하므로 영향이 없을 것으로 예상한다.
# 참고자료
https://github.com/spring-projects/spring-data-redis/issues/3391
https://spring.io/blog/2026/06/10/spring-boot-4
https://download.java.net/java/early_access/jdk27/docs/specs/jls/jls-13.html
https://github.com/spring-projects/spring-data-redis/blob/main/src/main/java/org/springframework/data/redis/connection/DefaultedRedisConnection.java
https://github.com/redisson/redisson/blob/master/redisson-spring/redisson-spring-data/redisson-spring-data-41/src/main/java/org/redisson/spring/data/connection/RedissonConnection.java
'Java > spring' 카테고리의 다른 글
| resilience4j-spring-boot, spring-cloud-circuitbreaker 어떤걸 사용해야할까 (0) | 2026.02.27 |
|---|---|
| 실전에서 OOP하기(트래픽 제어 구현) (2) | 2025.10.08 |
| RestClient 내부 ObjectMapper 설정 (2) | 2025.07.13 |
| spring boot RestClient 설정하기 (0) | 2025.01.29 |
| Spring bean 에서 다형성을 활용할 수 있을까 (0) | 2024.05.26 |
- Total
- Today
- Yesterday
- Spring
- db
- JPA
- programming
- MySQL
- DesignPattern
- javascript
- OOP
- 정규표현식
- TEST
- frontcode
- frontend개발환경
- code
- Kotlin
- servlet
- java
- Design Pattern
- spring cloud
- EffectiveJava
- Jackson
- JavaScript Core
- java8
- toby
- clean code
- backend개발환경
- Git
- http
- mariadb
- go-core
- generics
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 | 31 |

