Spring Boot 4.0으로 프로젝트를 마이그레이션을 하는데, Redis 설정에서 낯선 에러를 마주쳤다.
Unresolved reference 'DefaultTyping'.
분명히 이전 버전에서 동작이 잘 되었는데, 4.0으로 버전을 올린 후 왜 DefaultTyping 을 찾을 수 없나는 건지..
// 기존 코드 (Spring Boot 3.x)
private fun redisObjectMapper(): ObjectMapper =
JsonMapper.builder()
.addModule(KotlinModule.Builder().build())
.activateDefaultTyping(
BasicPolymorphicTypeValidator.builder()
.allowIfSubType(Any::class.java)
.build(),
ObjectMapper.DefaultTyping.NON_FINAL // ❗️ 에러 발생!
)
.build()
Spring Boot 4.0의 가장 큰 변화 중 하나는 Jackson 2에서 Jackson 3로의 전환이다.
| 버전 | 패키지 |
|---|---|
| Jackson 2.x | com.fasterxml.jackson |
| Jackson 3.x | tools.jackson |
Jackson 3에서는 DefaultTyping enum이 ObjectMapper 내부 클래스에서 별도 클래스로 분리가 되었다.
// Jackson 2.x
ObjectMapper.DefaultTyping.NON_FINAL
// Jackson 3.x
import tolls.jackson.databind.DefaultTyping
DefaultTyping.NON_FINAL
import를 수정하고 다시 빌드를 해보았다.
import tools.jackson.databind.DefaultTyping
import tools.jackson.databind.ObjectMapper
import tools.jackson.databind.json.JsonMapper
그런데 이번에는 다른 에러가 발생했다.
None of the following candidates is applicable:
constructor<T : Any!>(mapper: ObjectMapper, type: Class<T!>): Jackson2JsonRedisSerializer<T>
constructor<T : Any!>(mapper: ObjectMapper, javaType: JavaType): Jackson2JsonRedisSerializer<T>
Jackson2JsonRedisSerializer가 tools.jackson.databind.ObjectMapper를 받지 않는 것 같았다.
API 문서를 찾아보니
@Deprecated(since="4.0", forRemoval=true)
public class Jackson2JsonRedisSerializer<T>
Jackson2JsonRedisSerializer가 Spring Boot Redis 4.0에서 Deprecated가 되었다.
Spring Data Redis 4.0에서는 Jackson 3 전용 Serializer가 새로 추가되었다.
| Serializer | Jackson 버전 | 상태 |
|---|---|---|
Jackson2JsonRedisSerializer | Jackson 2.x (com.fasterxml.jackson) | Deprecated |
JacksonJsonRedisSerializer | Jackson 3.x (tools.jackson) | 권장 |
클래스 이름에서 2가 빼진 것이 핵심이다.
package com.example.dotzip.common.config
import org.springframework.beans.factory.annotation.Value
import org.springframework.cache.annotation.EnableCaching
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.data.redis.cache.RedisCacheConfiguration
import org.springframework.data.redis.cache.RedisCacheManager
import org.springframework.data.redis.connection.RedisConnectionFactory
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory
import org.springframework.data.redis.core.RedisTemplate
import org.springframework.data.redis.core.StringRedisTemplate
import org.springframework.data.redis.serializer.JacksonJsonRedisSerializer
import org.springframework.data.redis.serializer.RedisSerializationContext
import org.springframework.data.redis.serializer.StringRedisSerializer
import tools.jackson.databind.ObjectMapper
import tools.jackson.databind.json.JsonMapper
import tools.jackson.module.kotlin.KotlinModule
@Configuration
@EnableCaching
class RedisConfig {
@Value("\${spring.data.redis.host}")
private lateinit var host: String
@Value("\${spring.data.redis.port}")
private var port: Int = 6379
@Bean
fun redisConnectionFactory(): RedisConnectionFactory {
return LettuceConnectionFactory(host, port)
}
@Bean
fun redisObjectMapper(): ObjectMapper =
JsonMapper.builder()
.addModule(KotlinModule.Builder().build())
.build()
@Bean
fun redisCacheManager(
connectionFactory: RedisConnectionFactory,
redisObjectMapper: ObjectMapper
): RedisCacheManager {
val serializer = JacksonJsonRedisSerializer(redisObjectMapper, Any::class.java)
val cacheConfig = RedisCacheConfiguration.defaultCacheConfig()
.serializeKeysWith(
RedisSerializationContext.SerializationPair.fromSerializer(StringRedisSerializer())
)
.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer<Any>(serializer)
)
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(cacheConfig)
.build()
}
@Bean
fun redisTemplate(
connectionFactory: RedisConnectionFactory,
redisObjectMapper: ObjectMapper
): RedisTemplate<String, Any> {
val serializer = JacksonJsonRedisSerializer(redisObjectMapper, Any::class.java)
return RedisTemplate<String, Any>().apply {
setConnectionFactory(connectionFactory)
keySerializer = StringRedisSerializer()
valueSerializer = serializer
hashKeySerializer = StringRedisSerializer()
hashValueSerializer = serializer
}
}
@Bean
fun stringRedisTemplate(connectionFactory: RedisConnectionFactory): StringRedisTemplate {
return StringRedisTemplate(connectionFactory)
}
}
Spring Boot 3.x -> 4.0 Redis 설정 마이그레이션 시 확인해야 할 사항들:
// Before (Jackson 2)
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule
// After (Jackson 3)
import tools.jackson.databind.ObjectMapper
import tools.jackson.module.kotlin.KotlinModule
dependencies {
implementation("tools.jackson.module:jackson-module-kotlin")
implementation("org.springframework.boot:spring-boot-starter-data-redis")
}
메이저 버전 업그레이드 시 항상 공식 마이그레이션 가이드를 먼저 읽어보는 습관을 들이기로 한다.
에러가 발생하면 해당 클래스의 API 문서를 확인해보고 Deprecated 여부와 대체 클래스를 확인해 볼 수 있다.
| 항목 | Spring Boot 3.x | Spring Boot 4.0 |
|---|---|---|
| Jackson 버전 | 2.x | 3.x |
| 패키지 | com.fasterxml.jackson | tools.jackson |
| Redis Serializer | Jackson2JsonRedisSerializer | JacksonJsonRedisSerializer |
| DefaultTyping | ObjectMapper.DefaultTyping | tools.jackson.databind.DefaultTyping |
Spring Boot 4.0 마이그레이션은 생각보다 많은 변화가 있었다. 특히 Jackson 3 전환은 Redis, REST API, 메시지 직렬화 등 다양한 곳에 영향을 미치니 꼼꼼히 확인하는게 좋을 거 같다.