Spring Boot cookbook · Beginner

Cache service results in Redis with @Cacheable

Speed up slow product lookups with a shared Redis cache and per-cache TTLs.

Approach

Enable caching, configure Redis cache TTLs per cache name with JSON serialization, annotate read methods with @Cacheable and writes with @CacheEvict/@CachePut. Cache immutable DTOs (records), not JPA entities.

Solution

@Configuration
@EnableCaching
class CachingConfig {
    @Bean
    RedisCacheManagerBuilderCustomizer cacheTtls() {
        var json = RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.json());
        var base = RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(json).disableCachingNullValues();
        return builder -> builder
                .cacheDefaults(base.entryTtl(Duration.ofMinutes(5)))
                .withCacheConfiguration("product", base.entryTtl(Duration.ofMinutes(30)))
                .withCacheConfiguration("exchange-rates", base.entryTtl(Duration.ofSeconds(60)));
    }
}

@Service
class ProductService {
    @Cacheable(cacheNames = "product", key = "#id")
    public ProductResponse get(long id) {
        return repo.findById(id).map(ProductResponse::from).orElseThrow(() -> new NotFoundException("Product " + id));
    }

    @CacheEvict(cacheNames = "product", key = "#id")
    @Transactional
    public void rename(long id, String name) {
        repo.findById(id).orElseThrow().rename(name);
    }
}

Pitfalls

  • Calling the cached method from within the same class (proxy bypass).
  • Caching entities with lazy relations.
  • Evicting before the transaction commits (another request can re-cache stale data) — evict after commit for strictness.

Variations

  • Caffeine for local, ultra-fast caches.
  • @Cacheable(sync = true) to avoid stampedes on a single instance.

Further reading: Spring Boot: Caching

Reference: https://docs.spring.io/spring-boot/reference/io/caching.html

More Spring Boot recipes