Boost Spring Boot Performance with Multi‑Level Caffeine‑Redis Cache

This article explains why a multi‑level cache combining JVM‑level Caffeine and distributed Redis is essential for modern Spring Boot applications, outlines design challenges, provides step‑by‑step integration instructions, and demonstrates performance gains with benchmark results and core implementation code.

Java Architecture Diary
Java Architecture Diary
Java Architecture Diary
Boost Spring Boot Performance with Multi‑Level Caffeine‑Redis Cache

Why Multi‑Level Cache

Introducing caching is now a must for most systems. While Redis is a common middleware, large data sizes and complex structures can degrade performance, and network I/O becomes a significant bottleneck, especially in micro‑service architectures where a single request may trigger many downstream calls.

Caffeine, a high‑performance local in‑memory cache, offers considerably better speed than typical memory cache implementations.

Conclusion: we need to build an L1 Caffeine JVM‑level cache and an L2 Redis cache.

Design Challenges

Most application caches are based on Spring Cache with annotation support, which has the following limitations:

Spring Cache supports only a single cache provider, so Redis and Caffeine cannot be used simultaneously.

Data consistency issues between cache layers (e.g., application‑level cache vs. distributed cache).

Spring Cache does not support proactive expiration policies.

How to Use

Add the dependency:

<dependency>
    <groupId>com.pig4cloud.plugin</groupId>
    <artifactId>multilevel-cache-spring-boot-starter</artifactId>
    <version>0.0.1</version>
</dependency>

Enable cache support:

@EnableCaching
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}

Declare target interface with Spring Cache annotations:

@Cacheable(value = "get", key = "#key")
@GetMapping("/get")
public String get(String key) {
    return "success";
}

Performance Comparison

Benchmark environment: macOS Mojave, 2.3 GHz Intel Core i5, 8 GB RAM, Corretto 11 JDK, Redis running locally.

Results (operations per second):

Multi‑level implementation: 2716 ops/s

Default Redis only: 1373 ops/s

Code Principles

Custom CacheManager for Multi‑Level Cache

public class RedisCaffeineCacheManager implements CacheManager {
    @Override
    public Cache getCache(String name) {
        Cache cache = cacheMap.get(name);
        if (cache != null) {
            return cache;
        }
        cache = new RedisCaffeineCache(name, stringKeyRedisTemplate, caffeineCache(), cacheConfigProperties);
        Cache oldCache = cacheMap.putIfAbsent(name, cache);
        log.debug("create cache instance, the cache name is : {}", name);
        return oldCache == null ? cache : oldCache;
    }
}

Multi‑Level Read and Expiration Strategy

public class RedisCaffeineCache extends AbstractValueAdaptingCache {
    @Override
    protected Object lookup(Object key) {
        Object cacheKey = getKey(key);
        // 1. Try Caffeine first
        Object value = caffeineCache.getIfPresent(key);
        if (value != null) {
            log.debug("get cache from caffeine, the key is : {}", cacheKey);
            return value;
        }
        // 2. Fallback to Redis
        value = stringKeyRedisTemplate.opsForValue().get(cacheKey);
        if (value != null) {
            log.debug("get cache from redis and put in caffeine, the key is : {}", cacheKey);
            caffeineCache.put(key, value);
        }
        return value;
    }

    @Override
    public void put(Object key, Object value) {
        push(new CacheMessage(this.name, key));
    }

    @Override
    public void evict(Object key) {
        push(new CacheMessage(this.name, key));
    }

    @Override
    public void clear() {
        push(new CacheMessage(this.name, null));
    }

    private void push(CacheMessage message) {
        stringKeyRedisTemplate.convertAndSend(topic, message);
    }
}

Message Listener for Cache Invalidation

public class CacheMessageListener implements MessageListener {
    private final RedisTemplate<Object, Object> redisTemplate;
    private final RedisCaffeineCacheManager redisCaffeineCacheManager;

    @Override
    public void onMessage(Message message, byte[] pattern) {
        CacheMessage cacheMessage = (CacheMessage) redisTemplate.getValueSerializer().deserialize(message.getBody());
        redisCaffeineCacheManager.clearLocal(cacheMessage.getCacheName(), cacheMessage.getKey());
    }
}

Source Code

https://github.com/pig-mesh/multilevel-cache-spring-boot-starter

References

pig oauth2.0 client authentication: https://gitee.com/log4j/pig

Caffeine benchmark details: https://github.com/ben-manes/caffeine/wiki/Benchmarks

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

CacheredisSpring Bootmultilevel cache
Java Architecture Diary
Written by

Java Architecture Diary

Committed to sharing original, high‑quality technical articles; no fluff or promotional content.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.