Requiet
Requiet

Reputation: 75

Getting "class java.lang.String cannot be cast to class java.time.LocalDate" when reading from Redis cache

I'm using Spring Boot with Redis caching and Jackson2JsonRedisSerializer for serialization. My RedisConfig is set up like this:

@Configuration
@EnableCaching
public class RedisConfig {
    
    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JavaTimeModule());
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        return objectMapper;
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory, ObjectMapper objectMapper) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(objectMapper, Object.class);
        redisTemplate.setValueSerializer(serializer);
        redisTemplate.setHashValueSerializer(serializer);
        return redisTemplate;
    }

    @Bean
    public CacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory, ObjectMapper objectMapper) {
        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(objectMapper, Object.class);
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer));
        return RedisCacheManager.builder(redisConnectionFactory)
                .cacheDefaults(redisCacheConfiguration)
                .build();
    }
}

When caching LocalDate, it seems to be stored as a String in Redis. But when I try to read it back, I'm getting this error:

class java.lang.String cannot be cast to class java.time.LocalDate
(java.lang.String and java.time.LocalDate are in module java.base of loader 'bootstrap')

What am I missing? How can I ensure LocalDate is properly deserialized when reading from cache?


Update: Fixing redisCacheManager as shown below temporarily solved my issue, but I don't want to manually set it like this every time.

Is there a dynamic serializer/deserializer in Redis that prevents casting exceptions?

@Bean
    public CacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory, ObjectMapper objectMapper) {
        Jackson2JsonRedisSerializer<Object> defaultSerializer = new Jackson2JsonRedisSerializer<>(objectMapper, Object.class);
        RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig()
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(defaultSerializer));
        
        Map<String, Class<?>> cacheTypeMap = new HashMap<>();
        cacheTypeMap.put("initialFundPriceDate", LocalDate.class);
        cacheTypeMap.put("fundPriceMap", FundCardPriceDto.class);
        cacheTypeMap.put("currencyPriceMap", CurrencyPriceDto.class);
        cacheTypeMap.put("fundCardAvgShares", BigDecimal.class);
        cacheTypeMap.put("fundCardGenerateYield", BigDecimal.class);
        cacheTypeMap.put("fundCardStandardDev", BigDecimal.class);
        cacheTypeMap.put("fundCardDefinitionMap", FundDefinitionDto.class);

        Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<>();

        for (Map.Entry<String, Class<?>> entry : cacheTypeMap.entrySet()) {
            Jackson2JsonRedisSerializer<?> serializer =
                    new Jackson2JsonRedisSerializer<>(objectMapper, entry.getValue());
            RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig()
                    .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer));
            cacheConfigurations.put(entry.getKey(), cacheConfig);
        }
        
        return RedisCacheManager.builder(redisConnectionFactory)
                .cacheDefaults(defaultCacheConfig)
                .withInitialCacheConfigurations(cacheConfigurations)
                .build();
    }

Upvotes: 1

Views: 84

Answers (1)

Requiet
Requiet

Reputation: 75

I realized that my issue was caused by not implementing the Serializable interface. Since I was using a NoSQL database, I was typically returning responses as Map<String, Object>. However, this approach led to type conversion errors, especially when dealing with caching.

Solution: Instead of using a generic Map<String, Object>, I created a dedicated DTO for each method and implemented the Serializable interface in those DTOs. This ensured that objects could be correctly serialized and deserialized when stored in the cache.

Example of DTO Implementation:

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Document(collection = "BenchmarkYields")
public class BenchmarkYields implements Serializable {
        /**
         * 
         */
        private static final long serialVersionUID = -5094385646730237631L;
        @Field("fund_code")
        private String fundCode;
        @Field("date")
        private String date;
    ...

Here’s how I configured Redis Cache Manager and Redis Template:

@Bean
public RedisCacheManager cacheManager(LettuceConnectionFactory lettuceConnectionFactory) {
    RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration
            .defaultCacheConfig(Thread.currentThread().getContextClassLoader());
    return RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(lettuceConnectionFactory)
            .cacheDefaults(redisCacheConfiguration).build();
}

@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
    RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
    redisTemplate.setConnectionFactory(redisConnectionFactory);
    redisTemplate.setKeySerializer(new StringRedisSerializer());
    redisTemplate.setHashKeySerializer(new StringRedisSerializer());

    Jackson2JsonRedisSerializer<Object> serializer =
            new Jackson2JsonRedisSerializer<>(objectMapperService.getObjectMapper(), Object.class);

    redisTemplate.setValueSerializer(serializer);
    redisTemplate.setHashValueSerializer(serializer);
    return redisTemplate;
}

This approach completely resolved my issue, and I no longer get ClassCastException errors when retrieving cached data.

Upvotes: 0

Related Questions