I am trying to cache a Kotlin data class(serializable) to Redis, but I am getting this error-
org.springframework.data.redis.serializer.SerializationException: Cannot deserialize
at org.springframework.data.redis.serializer.JdkSerializationRedisSerializer.deserialize(JdkSerializationRedisSerializer.java:113) ~[spring-data-redis-3.5.1.jar:3.5.1]
I am using the Kotlin serialization plugin (version: 2.2.0) and dependency (version: 1.9.0) in my project. Spring 3.5.3
Service -
@Cacheable("Schedule")
suspend fun getTrainSchedule(trainNumber: String): TrainDetails? {
val data = trainDetailDao.getSchedule(trainNumber)
return data
}
Data class -
import kotlinx.serialization.Serializable
@Serializable
data class TrainDetails(
val no: String,
val na: String,
val typ: Int,
val dly: Int,
val zn: Int,
val rake: String,
val classes: Int,
val upd: String,
)
Spring’s default Redis caching uses JDK serialization, which doesn’t work with Kotlin data classes unless they implement Serializable. To store Kotlin @Serializable classes as JSON (String) in Redis, you can use a custom RedisSerializer built on Kotlinx Serialization.
import kotlinx.serialization.KSerializer
import kotlinx.serialization.json.Json
import kotlinx.serialization.encodeToString
import kotlinx.serialization.decodeFromString
import org.springframework.data.redis.serializer.RedisSerializer
import org.springframework.data.redis.serializer.SerializationException
import java.nio.charset.StandardCharsets
class KotlinxRedisSerializer<T : Any>(
private val serializer: KSerializer<T>,
private val json: Json = Json
) : RedisSerializer<T> {
override fun serialize(t: T?): ByteArray? {
return try {
if (t == null) return null
json.encodeToString(serializer, t).toByteArray(StandardCharsets.UTF_8)
} catch (e: Exception) {
throw SerializationException("Could not serialize: $t", e)
}
}
override fun deserialize(bytes: ByteArray?): T? {
return try {
if (bytes == null || bytes.isEmpty()) return null
val str = String(bytes, StandardCharsets.UTF_8)
json.decodeFromString(serializer, str)
} catch (e: Exception) {
throw SerializationException("Could not deserialize", e)
}
}
}
import kotlinx.serialization.serializer
inline fun <reified T : Any> kotlinxRedisSerializer(
json: Json = Json
): RedisSerializer<T> {
return KotlinxRedisSerializer(serializer = json.serializersModule.serializer(), json = json)
}
@Configuration
class RedisConfig {
@Bean
fun redisCacheManager(factory: RedisConnectionFactory): RedisCacheManager {
val serializer = kotlinxRedisSerializer<TrainTicket>()
val config = RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(serializer)
)
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build()
}
}
I also added a full working demo to the "redis-springboot-resources" repository: