java 109 lines · 3 tabs

Caching Currency-Conversion Rates in Spring Boot With @Cacheable and Scheduled Eviction

Shared by codesnips Jul 2026
3 tabs
package com.example.fx.config;

import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;

import java.time.Duration;

@Configuration
@EnableCaching
@EnableScheduling
public class CacheConfig {

    @Bean
    public CacheManager cacheManager() {
        CaffeineCacheManager manager = new CaffeineCacheManager("fxRates");
        manager.setCaffeine(Caffeine.newBuilder()
                .maximumSize(500)
                .expireAfterWrite(Duration.ofMinutes(5))
                .recordStats());
        return manager;
    }
}
3 files · java Explain with highlit

This snippet shows how a Spring Boot service caches expensive foreign-exchange rate lookups so that repeated conversions do not hammer a slow, rate-limited upstream provider. The pattern is Spring's declarative caching abstraction: methods annotated with @Cacheable transparently store their return value keyed by the arguments, and subsequent calls with the same key skip the method body entirely and return the cached value.

In CacheConfig, @EnableCaching activates the annotation processing, and a Caffeine-backed CacheManager bounds the cache with maximumSize and a expireAfterWrite TTL. Bounding matters: an unbounded cache of currency pairs would grow forever and a stale rate that never expires would be worse than a cache miss. The write-based expiry means a rate is trusted for five minutes regardless of read traffic, which is a sensible freshness window for FX quotes.

In CurrencyRateService, getRate carries @Cacheable(value = "fxRates", key = "#from + '-' + #to"). The explicit SpEL key composes a stable cache key from both arguments so that USD-EUR and EUR-USD never collide. The unless = "#result == null" guard prevents a failed or empty lookup from being cached, which avoids pinning a bad value for the whole TTL. The actual fetchRateFromProvider call is the expensive part being protected — a remote HTTP round trip.

The refreshAllRates method is annotated with @CacheEvict(value = "fxRates", allEntries = true) and driven by @Scheduled, so every five minutes the entire cache is flushed proactively. Combining a TTL with scheduled eviction gives two independent freshness mechanisms and a clean way to force-clear on demand.

In ConversionController, the endpoint simply multiplies an amount by the cached rate; it is unaware of caching, which is the whole point of the abstraction — cross-cutting concerns stay out of business logic. A key pitfall to remember is that @Cacheable and @CacheEvict rely on Spring's proxy, so self-invocation within the same bean bypasses the cache; calls must cross the proxy boundary to take effect. This approach fits any read-heavy lookup with an expensive, idempotent source and tolerable staleness.


Related snips

Share this code

Here's the card — post it anywhere.

Caching Currency-Conversion Rates in Spring Boot With @Cacheable and Scheduled Eviction — share card
Link copied