Spring Boot Caching With Valkey or Redis: A Complete @Cacheable Guide

Published on
July 6, 2026

The trickiest part about scaling enterprise applications is keeping them fast, and often, the first step toward improving response times is adding a caching layer. Developers who want to integrate caching into Spring Boot generally choose between two options. The first is the default Spring Data Redis stack, which features the basic Java client Lettuce under the hood. The second is Redisson, the Valkey and Redis Java client that provides over 60 distributed data structures along with a drop-in Spring Cache manager.

This guide is all about the Redisson approach. It will show you how redisson-spring-boot-starter integrates with Spring's caching abstraction. You'll learn how to configure @Cacheable, @CacheEvict, and @CachePut, manage time-to-live (TTL) settings, customize serialization, and implement a Near Cache for read-heavy workloads.

To cover all that, you'll learn how to build a BookService that returns book data (title, author, etc.) using Valkey/Redis-backed caching. We'll go over the configurable parameters and how to add an optional local cache for frequently accessed keys, all to show why Redisson is the premier choice for Spring Boot Caching in enterprise-grade distributed systems.

Why Choose Redisson?

If you're wondering why you would choose Redisson over the default Spring Data Redis, it helps to understand the fundamental differences between the two.

Spring Boot's spring-boot-starter-data-redis provides a functional RedisCacheManager for standard @Cacheable operations using the Lettuce client. Redisson serves a different architectural purpose by treating the remote server as a backing store for complex Java objects (maps, locks, and queues) while exposing a Spring Cache implementation on top of it. This makes Redisson the better choice if your application architectures require any of the following:

  • A Near Cache to reduce network latency on hot reads.
  • Distributed primitives, like locks or semaphores, integrated alongside the cache.
  • Asynchronous, reactive, and RxJava3 APIs utilizing the same underlying client.
  • Diverse serialization choices, including a highly performant binary codec enabled by default.

It all comes down to the differences between Lettuce and Redisson. Whereas Lettuce is perfectly fine if your only requirement is placing a value in Redis with a TTL, Redisson becomes part of your broader distributed caching strategy.

Spring Boot Caching With Redisson on Valkey/Redis: Step-by-Step

Now, let's look at the steps required to set up Spring Boot Caching with Redisson, backed with Valkey or Redis:

Step 1: Integrate the Redisson Spring Boot Starter

The Redisson starter requires only minimal setup and automatically configures a RedissonClient, a RedisConnectionFactory, and the necessary template beans. The latest release maintains compatibility across all supported Spring Boot generations.

For modern Maven setups targeting the latest Spring Boot, include the standard dependency:

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>4.6.0</version>
</dependency>

To target an older Spring Boot release while maintaining the latest starter, you must exclude the bundled data module and explicitly specify the version matching your Spring Data Redis environment:

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>4.6.0</version>
    <exclusions>
        <exclusion>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-data-40</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-data-27</artifactId>
    <version>4.5.0</version>
</dependency>

The equivalent setup in Gradle:

// Default: latest starter, targets the newest Spring Boot
implementation 'org.redisson:redisson-spring-boot-starter:4.6.0'

// Target an earlier Spring Boot line by swapping the bundled spring-data module:
implementation('org.redisson:redisson-spring-boot-starter:4.6.0') {
    exclude group: 'org.redisson', module: 'redisson-spring-data-40'
}
implementation 'org.redisson:redisson-spring-data-27:4.6.0' // pick the suffix for your Boot line

Connection Configuration

For single-node deployments, Redisson automatically reads standard Spring Boot properties:

# application.yml — Spring Boot 3.x / 4.x
spring:
  data:
    redis:
      host: localhost
      port: 6379
      # password: changeme
      # ssl:
      #   enabled: true

Note that on Spring Boot 2.7 and earlier, use the spring.redis.* structure, instead of spring.data.redis.* as shown here.

For clusters or sentinels, or to explicitly tune connection pools and retry policies, configure a dedicated redisson.yaml file and reference it within your application properties:

spring:
  redis:
    redisson:
      file: classpath:redisson.yaml

# redisson.yaml — single-server example
singleServerConfig:
  address: "redis://127.0.0.1:6379"
  connectionPoolSize: 64
  connectionMinimumIdleSize: 24

Step 2: Enable Spring Cache Annotations

Because Redisson implements Spring's CacheManager, caching operations are driven by standard Spring annotations. To enable this, activate caching and register a RedissonSpringCacheManager bean. Within this configuration, you can define individual caches with distinct TTL and maximum idle time parameters (defined in milliseconds):

import org.redisson.api.RedissonClient;
import org.redisson.spring.cache.CacheConfig;
import org.redisson.spring.cache.RedissonSpringCacheManager;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

@Configuration
@EnableCaching
public class RedissonCacheConfig {

    @Bean
    public CacheManager cacheManager(RedissonClient redissonClient) {
        Map config = new HashMap<>();

        // ttl = 10 min, maxIdleTime = 5 min
        config.put("books", new CacheConfig(10 * 60 * 1000, 5 * 60 * 1000));

        // ttl = 30 min, no idle expiry (0 = disabled)
        config.put("authors", new CacheConfig(30 * 60 * 1000, 0));

        return new RedissonSpringCacheManager(redissonClient, config);
    }
}

Once configured, @Cacheable intercepts method calls, returning the cached value on a hit, or executing the method and storing the result on a miss.

@Service
public class BookService {
    private final BookRepository repository;

    public BookService(BookRepository repository) {
        this.repository = repository;
    }

    @Cacheable(value = "books", key = "#isbn")
    public Book findByIsbn(String isbn) {
        // Only runs on a cache miss. Imagine a slow DB / external call here.
        return repository.loadFromDatabase(isbn);
    }

To force an update to the cached data, use @CachePut, which executes the method and writes the new return value to the cache:

@CachePut(value = "books", key = "#book.isbn")
    public Book update(Book book) {
        return repository.save(book);
    }

To remove specific entries or clear the cache, use @CacheEvict:

// Remove a single entry
    @CacheEvict(value = "books", key = "#isbn")
    public void delete(String isbn) {
        repository.deleteByIsbn(isbn);
    }

    // Clear the whole cache
    @CacheEvict(value = "books", allEntries = true)
    public void clearAllBooks() {
        // e.g. after a bulk import
    }
}

You can also gate caching with SpEL conditions:
@Cacheable(value = "books", key = "#isbn", condition = "#isbn.length() > 13", unless = "#result == null") — to skip caching for invalid keys or null results.

Step 3: Managing TTL Policies and Serialization

Define your cache expiration policies in a YAML file:

# cache-config.yaml
books:
  ttl: 600000          # 10 minutes
  maxIdleTime: 300000  # 5 minutes
authors:
  ttl: 1800000         # 30 minutes
  maxIdleTime: 0

Update your CacheManager bean to reference this file:

@Bean
public CacheManager cacheManager(RedissonClient redissonClient) throws IOException {
    return new RedissonSpringCacheManager(redissonClient, "classpath:/cache-config.yaml");
}

By default, Redisson serializes cache values with Kryo5Codec, a compact binary format. If you need human-readable data for debugging or cross-language interoperability, you can change this to a JSON codec in redisson.yaml:

codec: !<org.redisson.codec.JsonJacksonCodec> {}

Alternatively, you can configure it programmatically:

Config config = new Config();
config.setCodec(new org.redisson.codec.JsonJacksonCodec());
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
RedissonClient client = Redisson.create(config);

Step 4: Optimize Read Performance With a Near Cache

A Near Cache stores a local, in-JVM copy of frequently accessed data, allowing reads to bypass the network altogether. Redisson broadcasts invalidation events over a Valkey/Redis Pub/Sub channel, and whenever a key is mutated, all other instances automatically drop or update their local copies.

In Redisson's free community edition, you can achieve this read speed by utilizing RLocalCachedMap directly in code for read-heavy reference data:

import org.redisson.api.RLocalCachedMap;
import org.redisson.api.options.LocalCachedMapOptions;
import org.redisson.api.options.LocalCachedMapOptions.EvictionPolicy;
import org.redisson.api.options.LocalCachedMapOptions.SyncStrategy;
import java.time.Duration;

@Service
public class HotBookCache {
    private final RLocalCachedMap hotBooks;

    public HotBookCache(RedissonClient redisson) {
        LocalCachedMapOptions options = LocalCachedMapOptions.name("hot:books")
            .cacheSize(1_000)                       // max local entries (0 = unbounded)
            .evictionPolicy(EvictionPolicy.LRU)     // LRU / LFU / SOFT / WEAK / NONE
            .timeToLive(Duration.ofMinutes(10))     // local entry TTL
            .syncStrategy(SyncStrategy.INVALIDATE); // drop local copy on remote change

        this.hotBooks = redisson.getLocalCachedMap(options);
    }

    public Book get(String isbn) {
        return hotBooks.get(isbn);   // served from local memory after the first load
    }

    public void put(Book book) {
        hotBooks.fastPut(book.getIsbn(), book); // fastPut skips returning the old value
    }
}

Redisson PRO and Advanced Local-Cached Options

If your app requires transparent near-caching driven by the @Cacheable annotation, the advanced Redisson PRO client offers specialized managers that natively handle data partitioning and advanced eviction strategies.

RedissonSpringLocalCachedCacheManager is the basic starting point. The rest of Redisson PRO's local-cached options support more advanced functionality. Since this and the other cache managers implement Spring's standard CacheManager, upgrading only requires a one-line bean swap; your @Cacheable, @CacheEvict, and @CachePut annotations remain the same.

The advanced local-cached cache managers in Redisson PRO include:

  • RedissonClusteredSpringLocalCachedCacheManager combines a Near Cache with data partitioning and scripted eviction. This is an excellent option when a hot cache is too large for a single cluster node, but you still want local copies for fast reads.
  • RedissonSpringLocalCachedCacheV2Manager offers near-caching and data partitioning. Its advanced eviction features allow for server-side expiration rather than via a scripted task. Note that this option does not require Valkey 9.0+ or Redis 7.4+ like the next two on this list.
  • RedissonSpringLocalCachedCacheNativeManager provides near-caching with native eviction, meaning Valkey or Redis expires entries directly, reducing operational overhead. As noted above, this configuration requires Valkey 9.0+ or Redis 7.4+.
  • RedissonClusteredSpringLocalCachedCacheNativeManager is the most comprehensive option. It combines near-caching, data partitioning, and native eviction. This is the recommended choice for large clusters running modern Valkey/Redis versions, and you need maximum horizontal scaling.

Step 5: Put It All Together for a Working Demo

Now, you can integrate all the components to make a working demo of the BookService. Assuming the dependency and application configurations from Step 1 are in place, and Valkey or Redis is running, start by establishing a basic model:

public class Book implements java.io.Serializable {
    private String isbn;
    private String title;
    private String author;

    public Book() {}                  // no-arg ctor (needed for JSON codec)

    public Book(String isbn, String title, String author) {
        this.isbn = isbn; this.title = title; this.author = author;
    }

    // getters & setters omitted for brevity
    public String getIsbn() { return isbn; }
}

Next, create a repository that simulates a slow data source:

@Repository
public class BookRepository {
    public Book loadFromDatabase(String isbn) {
        try { Thread.sleep(2000); } catch (InterruptedException ignored) {} // pretend it's slow
        return new Book(isbn, "Effective Java", "Joshua Bloch");
    }

    public Book save(Book book) { /* persist */ return book; }
    public void deleteByIsbn(String isbn) { /* delete */ }
}

Finally, bind the cached service (BookService from Step 2) to a REST controller:

@RestController
@RequestMapping("/books")
public class BookController {
    private final BookService service;

    public BookController(BookService service) { this.service = service; }

    @GetMapping("/{isbn}")
    public Book get(@PathVariable String isbn) {
        return service.findByIsbn(isbn);
    }

    @PutMapping
    public Book update(@RequestBody Book book) {
        return service.update(book);
    }

    @DeleteMapping("/{isbn}")
    public void delete(@PathVariable String isbn) {
        service.delete(isbn);
    }
}

Now, make some requests to this service. In this example, the first call pays the "database penalty" and should have a delay of about two seconds, but the second returns almost instantly:

# First request: ~2s (cache miss, hits the "database")
time curl http://localhost:8080/books/9780134685991

# Second request: a few ms (served from Redis)
time curl http://localhost:8080/books/9780134685991

# Verify the key landed in Redis
redis-cli KEYS "books*"

Redisson: Caching for Distributed Applications

Now you have a complete @Cacheable setup on Spring Boot with Redisson, and you can see the difference it makes for enterprise applications. While Redisson has a larger footprint in your tech stack than Lettuce, it also allows you to build a modern caching strategy suited to today's distributed system architectures. Implementing near-caching, advanced serialization, and distributed data structures in Redisson is just like working with other Java objects. To learn more about Redisson Community Edition and Redisson PRO, visit this feature comparison.

Similar articles