Skip to content

Integration with Spring

Spring Boot Starter

Integrates Redisson with Spring Boot library. Depends on Spring Data Redis module.

Supports Spring Boot 1.3.x - 4.1.x

Usage

  1. Add redisson-spring-boot-starter dependency into your project:

    • Community Edition

      Maven

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

      Gradle

      compile 'org.redisson:redisson-spring-boot-starter:4.7.0'
      
    • Redisson PRO

      Maven

      <dependency>
         <groupId>pro.redisson</groupId>
         <artifactId>redisson-spring-boot-starter</artifactId>
         <version>4.7.0</version>
      </dependency>
      

      Gradle

      compile 'pro.redisson:redisson-spring-boot-starter:4.7.0'
      

      License key configuration

    Redisson PRO vs. Community Edition ➜

    redisson-spring-boot-starter depends on redisson-spring-data module compatible with the latest version of Spring Boot. Downgrade redisson-spring-data module if necessary to support previous Spring Boot versions:

    redisson-spring-data
    module name
    Spring Boot
    version
    redisson-spring-data-16 1.3.y
    redisson-spring-data-17 1.4.y
    redisson-spring-data-18 1.5.y
    redisson-spring-data-2x 2.x.y
    redisson-spring-data-3x 3.x.y
    redisson-spring-data-4x 4.x.y

    For Gradle, you can downgrade to redisson-spring-data-27 this way:

    implementation ("org.redisson:redisson-spring-boot-starter:4.7.0") {
       exclude group: 'org.redisson', module: 'redisson-spring-data-40'
    }
    implementation "org.redisson:redisson-spring-data-27:4.7.0"
    

    For Maven, you can downgrade to redisson-spring-data-27 this way:

    <dependencies>
        <dependency>
            <groupId>org.redisson</groupId>
            <artifactId>redisson-spring-boot-starter</artifactId>
            <version>4.7.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.7.0</version>
        </dependency>
    </dependencies>
    
  2. Add settings into application.settings file:

    Using common Spring Boot 3.x+ settings:

    spring:
      data:
        redis:
          database: 
          host:
          port:
          password:
          ssl: 
          timeout:
          connectTimeout:
          clientName:
          cluster:
            nodes:
          sentinel:
            master:
            nodes:
    

    Using common Spring Boot up to 2.7.x settings:

    spring:
      redis:
        database: 
        host:
        port:
        password:
        ssl: 
        timeout:
        connectTimeout:
        clientName:
        cluster:
          nodes:
        sentinel:
          master:
          nodes:
    

    Using Redisson config file: (single mode, replicated mode, cluster mode, sentinel mode, proxy mode, multi cluster mode, multi sentinel mode)

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

    Using Redisson settings: (single mode, replicated mode, cluster mode, sentinel mode, proxy mode, multi cluster mode, multi sentinel mode)

    spring:
      redis:
       redisson: 
          config: |
            clusterServersConfig:
              idleConnectionTimeout: 10000
              connectTimeout: 10000
              timeout: 3000
              retryAttempts: 3
              retryInterval: 1500
              failedSlaveReconnectionInterval: 3000
              failedSlaveCheckInterval: 60000
              password: null
              subscriptionsPerConnection: 5
              clientName: null
              loadBalancer: !<org.redisson.connection.balancer.RoundRobinLoadBalancer> {}
              subscriptionConnectionMinimumIdleSize: 1
              subscriptionConnectionPoolSize: 50
              slaveConnectionMinimumIdleSize: 24
              slaveConnectionPoolSize: 64
              masterConnectionMinimumIdleSize: 24
              masterConnectionPoolSize: 64
              readMode: "SLAVE"
              subscriptionMode: "SLAVE"
              nodeAddresses:
              - "redis://127.0.0.1:7004"
              - "redis://127.0.0.1:7001"
              - "redis://127.0.0.1:7000"
              scanInterval: 1000
              pingConnectionInterval: 0
              keepAlive: false
              tcpNoDelay: false
            threads: 16
            nettyThreads: 32
            codec: !<org.redisson.codec.Kryo5Codec> {}
            transportMode: "NIO"
    
  3. Available Spring Beans:

    • RedissonClient
    • RedissonRxClient
    • RedissonReactiveClient
    • RedisTemplate
    • ReactiveRedisTemplate
    • ReactiveRedisOperations

FAQ

Q: How to replace Netty version brought by Spring Boot?

You need to define netty version in properties section of your Maven project.

    <properties>
          <netty.version>4.2.9.Final</netty.version> 
    </properties>

Q: How to disable Redisson?

You may not have Redis or Valkey in some environments. In this case Redisson can be disabled:

  • Using Annotations
    Spring Boot 4.0+
    @SpringBootApplication
    @EnableAutoConfiguration(exclude = {
        RedissonAutoConfigurationV4.class})
    public class Application {
    
        public static void main(String[] args) {
            SpringApplication.run(MyApplication.class, args);
        }
    }
    
    Spring Boot 2.7+
    ```java
    @SpringBootApplication
    @EnableAutoConfiguration(exclude = {
        RedissonAutoConfigurationV2.class})
    public class Application {
    
        public static void main(String[] args) {
            SpringApplication.run(MyApplication.class, args);
        }
    }
    
    Spring Boot up to 2.6
    @SpringBootApplication
    @EnableAutoConfiguration(exclude = {
        RedissonAutoConfiguration.class})
    public class Application {
    
        public static void main(String[] args) {
            SpringApplication.run(MyApplication.class, args);
        }
    }
    
  • Using application.yml file
    Spring Boot 4.0+
    spring:
      autoconfigure:
        exclude:
          - org.redisson.spring.starter.RedissonAutoConfigurationV4
    
    Spring Boot 2.7+
    spring:
      autoconfigure:
        exclude:
          - org.redisson.spring.starter.RedissonAutoConfigurationV2
    
    Spring Boot up to 2.6
    spring:
      autoconfigure:
        exclude:
          - org.redisson.spring.starter.RedissonAutoConfiguration
    

Spring Cache

Redisson provides various Spring Cache implementations.

Eviction, local cache and data partitioning

Redisson provides various Spring Cache managers with multiple important features:

  1. Local cache

    So called near cache used to speed up read operations and avoid network roundtrips. It caches Map entries on Redisson side and executes read operations up to 45x faster in comparison with common implementation. Local cache instances with the same name connected to the same pub/sub channel. This channel is used for exchanging of update/invalidate events between all instances. Local cache store doesn't use hashCode()/equals() methods of key object, instead it uses hash of serialized state.

  2. Data partitioning

    Although Map object is cluster compatible its content isn't scaled/partitioned across multiple Redis or Valkey master nodes in cluster. Data partitioning allows to scale available memory, read/write operations and entry eviction process for individual Map instance in cluster.

  3. Scripted eviction

    Allows to define time to live or max idle time parameters per map entry. Eviction is done on Redisson side through a custom scheduled task which removes expired entries using Lua script. Eviction task is started once per unique object name at the moment of getting Map instance. If instance isn't used and has expired entries it should be get again to start the eviction process. This leads to extra Redis or Valkey calls and eviction task per unique map object name.

    Entries are cleaned time to time by org.redisson.eviction.EvictionScheduler. By default, it removes 100 expired entries at a time. This can be changed through cleanUpKeysAmount setting. Task launch time tuned automatically and depends on expired entries amount deleted in previous time and varies between 5 second to 30 minutes by default. This time interval can be changed through minCleanUpDelay and maxCleanUpDelay. For example, if clean task deletes 100 entries each time it will be executed every 5 seconds (minimum execution delay). But if current expired entries amount is lower than previous one then execution delay will be increased by 1.5 times and decreased otherwise.

    Available implementations:

    Class name Local
    cache
    Data
    partitioning
    Ultra-fast
    read/write
    RedissonSpringCacheManager
    open-source version
    RedissonSpringCacheManager
    Redisson PRO version
    ✔️
    RedissonSpringLocalCachedCacheManager
    available only in Redisson PRO
    ✔️ ✔️
    RedissonClusteredSpringCacheManager
    available only in Redisson PRO
    ✔️ ✔️
    RedissonClusteredSpringLocalCachedCacheManager
    available only in Redisson PRO
    ✔️ ✔️ ✔️
  4. Advanced eviction

    Allows to define time to live parameter per map entry. Doesn't use an entry eviction task, entries are cleaned on Redis or Valkey side.

    Available implementations:

    Class name Local
    cache
    Data
    partitioning
    Ultra-fast
    read/write
    RedissonSpringCacheV2Manager
    available only in Redisson PRO
    ✔️ ✔️
    RedissonSpringLocalCachedCacheV2Manager
    available only in Redisson PRO
    ✔️ ✔️ ✔️
  5. Native eviction

    Allows to define time to live parameter per map entry. Doesn't use an entry eviction task, entries are cleaned on Redis side.
    Requires Valkey 9.0+ or Redis 7.4+.

    Available implementations:

    Class name Local
    cache
    Data
    partitioning
    Ultra-fast
    read/write
    RedissonSpringCacheNativeManager
    open-source version
    RedissonSpringCacheNativeManager
    Redisson PRO version
    ✔️
    RedissonSpringLocalCachedCacheNativeManager
    available only in Redisson PRO
    ✔️ ✔️
    RedissonClusteredSpringCacheNativeManager
    available only in Redisson PRO
    ✔️ ✔️
    RedissonClusteredSpringLocalCachedCacheNativeManager
    available only in Redisson PRO
    ✔️ ✔️ ✔️

Local cache options

Follow options object can be supplied during local cached managers initialization:

LocalCachedMapOptions options = LocalCachedMapOptions.defaults()

// Defines whether to store a cache miss into the local cache.
// Default value is false.
.storeCacheMiss(false);

// Defines store mode of cache data.
// Follow options are available:
// LOCALCACHE - store data in local cache only.
// LOCALCACHE_REDIS - store data in both Redis or Valkey and local cache.
.storeMode(StoreMode.LOCALCACHE_REDIS)

// Defines Cache provider used as local cache store.
// Follow options are available:
// REDISSON - uses Redisson own implementation
// CAFFEINE - uses Caffeine implementation
.cacheProvider(CacheProvider.REDISSON)

 // Defines local cache eviction policy.
 // Follow options are available:
 // LFU - Counts how often an item was requested. Those that are used least often are discarded first.
 // LRU - Discards the least recently used items first
 // SOFT - Uses weak references, entries are removed by GC
 // WEAK - Uses soft references, entries are removed by GC
 // NONE - No eviction
.evictionPolicy(EvictionPolicy.NONE)

 // If cache size is 0 then local cache is unbounded.
.cacheSize(1000)

 // Used to load missed updates during any connection failures to Redis. 
 // Since, local cache updates can't be get in absence of connection to Redis. 
 // Follow reconnection strategies are available:
 // CLEAR - Clear local cache if map instance has been disconnected for a while.
 // LOAD - Store invalidated entry hash in invalidation log for 10 minutes
 //        Cache keys for stored invalidated entry hashes will be removed 
 //        if LocalCachedMap instance has been disconnected less than 10 minutes
 //        or whole cache will be cleaned otherwise.
 // NONE - Default. No reconnection handling
.reconnectionStrategy(ReconnectionStrategy.NONE)

 // Used to synchronize local cache changes.
 // Follow sync strategies are available:
 // INVALIDATE - Default. Invalidate cache entry across all LocalCachedMap instances on map entry change
 // UPDATE - Insert/update cache entry across all LocalCachedMap instances on map entry change
 // NONE - No synchronizations on map changes
.syncStrategy(SyncStrategy.INVALIDATE)

// time to live for each entry in local cache
.timeToLive(Duration.ofSeconds(10))

// max idle time for each entry in local cache
.maxIdle(Duration.ofSeconds(10));

// Defines how to listen expired event sent by Redis or Valkey upon this instance deletion
//
// Follow expiration policies are available:
// DONT_SUBSCRIBE - Don't subscribe on expire event
// SUBSCRIBE_WITH_KEYEVENT_PATTERN - Subscribe on expire event using `__keyevent@*:expired` pattern
// SUBSCRIBE_WITH_KEYSPACE_CHANNEL - Subscribe on expire event using `__keyspace@N__:name` channel
.expirationEventPolicy(ExpirationEventPolicy.SUBSCRIBE_WITH_KEYEVENT_PATTERN)

Usage

  1. Add redisson-spring-cache dependency into your project:

    • Community Edition

      Maven

      <dependency>
          <groupId>org.redisson</groupId>
          <artifactId>redisson-spring-cache</artifactId>
          <version>xVERSIONx</version>
      </dependency>
      

      Gradle

      compile 'org.redisson:redisson-spring-cache:xVERSIONx'
      
    • Redisson PRO

      Maven

      <dependency>
          <groupId>pro.redisson</groupId>
          <artifactId>redisson-spring-cache</artifactId>
          <version>xVERSIONx</version>
      </dependency>
      

      Gradle

      compile 'pro.redisson:redisson-spring-cache:xVERSIONx'
      

      License key configuration

    Redisson PRO vs. Community Edition ➜

  2. Create Spring Cache Manager instance

    Each Spring Cache Manager instance has two important parameters: ttl and maxIdleTime and stores data infinitely if they are not defined or equal to 0.

    Complete config example:

    @Configuration
    @ComponentScan
    @EnableCaching
    public static class Application {
    
        @Bean(destroyMethod="shutdown")
        RedissonClient redisson() throws IOException {
            Config config = new Config();
            config.useClusterServers()
                  .addNodeAddress("redis://127.0.0.1:7004", "redis://127.0.0.1:7001");
            return Redisson.create(config);
        }
    
        @Bean
        CacheManager cacheManager(RedissonClient redissonClient) {
            Map<String, CacheConfig> config = new HashMap<String, CacheConfig>();
    
            // define local cache settings for "testMap" cache.
            // ttl = 48 minutes and maxIdleTime = 24 minutes for local cache entries
            LocalCachedMapOptions options = LocalCachedMapOptions.defaults()
                .evictionPolicy(EvictionPolicy.LFU)
                .timeToLive(48, TimeUnit.MINUTES)
                .maxIdle(24, TimeUnit.MINUTES)
                .cacheSize(1000);
    
            // create "testMap" Redis or Valkey cache with ttl = 24 minutes and maxIdleTime = 12 minutes
            LocalCachedCacheConfig cfg = new LocalCachedCacheConfig(24*60*1000, 12*60*1000, options);
            // Max size of map stored in Redis
            cfg.setMaxSize(2000);
            config.put("testMap", cfg);
    
            // scripted eviction
            return new RedissonSpringCacheManager(redissonClient, config);
    
            // native eviction
            return new RedissonSpringCacheManager(redissonClient, config);
    
            // data partitioning + scripted eviction
            return new RedissonClusteredSpringCacheManager(redissonClient, config);
    
            // data partitioning + advanced eviction
            return new RedissonSpringCacheV2Manager(redissonClient, config);
    
            // data partitioning + native eviction
            return new RedissonClusteredSpringCacheNativeManager(redissonClient, config);
    
            // local cache + scripted eviction
            return new RedissonSpringLocalCachedCacheManager(redissonClient, config);
    
            // local cache + native eviction
            return new RedissonSpringLocalCachedCacheNativeManager(redissonClient, config);
    
            // local cache + data partitioning + native eviction
            return new RedissonClusteredSpringLocalCachedCacheNativeManager(redissonClient, config);
    
            // local cache + data partitioning + advanced eviction
            return new RedissonSpringLocalCachedCacheV2Manager(redissonClient, config);
    
            // local cache + data partitioning + scripted eviction
            return new RedissonClusteredSpringLocalCachedCacheManager(redissonClient, config);
        }
    
    }
    

    Cache configuration could be read from YAML configuration files:

        @Configuration
        @ComponentScan
        @EnableCaching
        public static class Application {
    
            @Bean(destroyMethod="shutdown")
            RedissonClient redisson(@Value("classpath:/redisson.yaml") Resource configFile) throws IOException {
                Config config = Config.fromYAML(configFile.getInputStream());
                return Redisson.create(config);
            }
    
            @Bean
            CacheManager cacheManager(RedissonClient redissonClient) throws IOException {
                // scripted eviction
                return new RedissonSpringCacheManager(redissonClient, "classpath:/cache-config.yaml");
    
                // native eviction
                return new RedissonSpringCacheManager(redissonClient, "classpath:/cache-config.yaml");
    
                // data partitioning + scripted eviction
                return new RedissonClusteredSpringCacheManager(redissonClient, "classpath:/cache-config.yaml");
    
                // data partitioning + advanced eviction
                return new RedissonSpringCacheV2Manager(redissonClient, "classpath:/cache-config.yaml");
    
                // data partitioning + native eviction
                return new RedissonClusteredSpringCacheNativeManager(redissonClient, "classpath:/cache-config.yaml");
    
                // local cache + scripted eviction
                return new RedissonSpringLocalCachedCacheManager(redissonClient, "classpath:/cache-config.yaml");
    
                // local cache + native eviction
                return new RedissonSpringLocalCachedCacheNativeManager(redissonClient, "classpath:/cache-config.yaml");
    
                // local cache + data partitioning + native eviction
                return new RedissonClusteredSpringLocalCachedCacheNativeManager(redissonClient, "classpath:/cache-config.yaml");
    
                // local cache + data partitioning + advanced eviction
                return new RedissonSpringLocalCachedCacheV2Manager(redissonClient, "classpath:/cache-config.yaml");
    
                // local cache + data partitioning + scripted eviction
                return new RedissonClusteredSpringLocalCachedCacheManager(redissonClient, "classpath:/cache-config.yaml");
            }
    
        }
    

YAML config format

Below is the configuration of Spring Cache with name testMap in YAML format:

---
testMap:
  ttl: 1440000
  maxIdleTime: 720000
  localCacheOptions:
    invalidationPolicy: "ON_CHANGE"
    evictionPolicy: "NONE"
    cacheSize: 0
    timeToLiveInMillis: 0
    maxIdleInMillis: 0

Note

localCacheOptions settings are available for RedissonSpringLocalCachedCacheManager, RedissonClusteredSpringLocalCachedCacheManager, RedissonSpringLocalCachedCacheV2Manager, RedissonSpringLocalCachedCacheNativeManager, RedissonClusteredSpringLocalCachedCacheNativeManager classes only.

Spring Session

Redisson integrates with Spring Session by providing RedissonConnectionFactory, which implements Spring Data Redis's RedisConnectionFactory and ReactiveRedisConnectionFactory interfaces. This allows Spring Session to use Redisson as the underlying Valkey or Redis client for session storage and retrieval.

Dependencies

Ensure you have Spring Session library in your classpath, add it if necessary:

  1. Add Spring Session Data Redis library in classpath:
    Maven:
    <dependency>
      <groupId>org.springframework.session</groupId>
      <artifactId>spring-session-data-redis</artifactId>
      <version>4.7.0</version>
    </dependency>
    
    Gradle:
    compile 'org.springframework.session:spring-session-data-redis:4.7.0'  
    
  2. Add Redisson Spring Data Redis library in classpath:

    • Community Edition

      Maven

      <dependency>
         <groupId>org.redisson</groupId>
         <artifactId>redisson-spring-data-40</artifactId>
         <version>4.7.0</version>
      </dependency>
      

      Gradle

      compile 'org.redisson:redisson-spring-data-40:4.7.0'
      
    • Redisson PRO

      Maven

      <dependency>
         <groupId>pro.redisson</groupId>
         <artifactId>redisson-spring-data-40</artifactId>
         <version>4.7.0</version>
      </dependency>
      

      Gradle

      compile 'pro.redisson:redisson-spring-data-40:4.7.0'
      

      License key configuration

    Redisson PRO vs. Community Edition ➜

Note

Valkey or Redis notify-keyspace-events setting should contain Exg letters to make Spring Session integration work.

Spring Http Session configuration

Add configuration class which extends AbstractHttpSessionApplicationInitializer class:

@Configuration
@EnableRedisHttpSession
public class SessionConfig extends AbstractHttpSessionApplicationInitializer { 

     @Bean
     public RedissonConnectionFactory redissonConnectionFactory(RedissonClient redisson) {
         return new RedissonConnectionFactory(redisson);
     }

     @Bean(destroyMethod = "shutdown")
     public RedissonClient redisson(@Value("classpath:/redisson.yaml") Resource configFile) throws IOException {
        Config config = Config.fromYAML(configFile.getInputStream());
        return Redisson.create(config);
     }

}

Spring WebFlux’s Session configuration

Add configuration class which extends AbstractReactiveWebInitializer class:

@Configuration
@EnableRedisWebSession
public class SessionConfig extends AbstractReactiveWebInitializer { 

     @Bean
     public RedissonConnectionFactory redissonConnectionFactory(RedissonClient redisson) {
         return new RedissonConnectionFactory(redisson);
     }

     @Bean(destroyMethod = "shutdown")
     public RedissonClient redisson(@Value("classpath:/redisson.yaml") Resource configFile) throws IOException {
        Config config = Config.fromYAML(configFile.getInputStream());
        return Redisson.create(config);
     }
}

Spring Boot configuration

  1. Add Redisson Spring Boot Starter module.

  2. Define the following properties in Spring Boot settings:

    spring.session.store-type=redis
    spring.redis.redisson.file=classpath:redisson.yaml
    spring.session.timeout.seconds=900
    

Local Cache

This feature is available only in Redisson PRO edition.

Redisson PRO provides local caching implementation for Spring Session.

local cache - a so-called near cache used to speed up read operations and avoid network roundtrips. It caches the whole Spring Session on the Redisson side and then uses the cached copy instead of loading it on each request.getSession() method access. The cached Session is updated/removed via pub/sub notifications.

To utilize this feature follow the steps below.

  1. Add Redisson Spring Session library in classpath.

    Maven

    <dependency>
       <groupId>pro.redisson</groupId>
       <!-- for Spring Session v2.x.x - v2.1.x -->
       <artifactId>redisson-spring-session-20</artifactId>
       <!-- for Spring Session v2.2.x - v2.7.x -->
       <artifactId>redisson-spring-session-22</artifactId>
       <!-- for Spring Session v3.x.x - v3.2.x -->
       <artifactId>redisson-spring-session-30</artifactId>
       <!-- for Spring Session v3.3.x - v4.x.x -->
       <artifactId>redisson-spring-session-33</artifactId>
       <version>4.7.0</version>
    </dependency>
    

    Gradle

    // for Spring Session v2.x.x - v2.1.x
    compile 'pro.redisson:redisson-spring-session-20:4.7.0'
    // for Spring Session v2.2.x - v2.7.x
    compile 'pro.redisson:redisson-spring-session-22:4.7.0'
    // for Spring Session v3.x.x - v3.2.x
    compile 'pro.redisson:redisson-spring-session-30:4.7.0'
    // for Spring Session v3.3.x - v4.x.x
    compile 'pro.redisson:redisson-spring-session-33:4.7.0'
    
  2. Define configuration

    Use @EnableLocalCachedRedisSession annotation for Redis-backed HTTP session management with local caching.
    Use @EnableLocalCachedRedisWebSession annotation for Redis-backed Spring WebFlux’s WebSession management with local caching.

    @Configuration
    @EnableLocalCachedRedisSession 
    // or 
    @EnableLocalCachedRedisWebSession
    public class SessionConfig extends AbstractHttpSessionApplicationInitializer { 
    
        @Bean
        public RedissonConnectionFactory redissonConnectionFactory(RedissonClient redisson) {
            return new RedissonConnectionFactory(redisson);
        }
    
        @Bean(destroyMethod = "shutdown")
        public RedissonClient redisson(@Value("classpath:/redisson.yaml") Resource configFile) throws IOException {
           Config config = Config.fromYAML(configFile.getInputStream());
           return Redisson.create(config);
        }
    
    }
    

    Configuration Settings

    The @EnableLocalCachedRedisSession annotation provides the following configuration settings:

    • maxInactiveIntervalInSeconds - Specifies the maximum time, in seconds, that a session can remain idle before it expires. Default is 1800.

    • namespace - Specifies a custom namespace for Redis keys to enable session isolation across multiple applications. The namespace changes the Redis key prefix from the default spring:session: to <redisNamespace>:. This allows multiple applications to share the same Redis instance while maintaining separate session storage. Default is "spring:session".

    • flushMode - Specifies when session changes are written to Redis. Default is FlushMode.ON_SAVE. Available modes:

      • FlushMode.ON_SAVE (default) - Session changes are written to Redis only when SessionRepository.save(Session) is explicitly invoked. In web applications, this occurs automatically just before the HTTP response is committed. This mode offers better performance by batching updates.
      • FlushMode.IMMEDIATE - Session changes are written to Redis immediately as they occur. Use this mode when session data must be immediately visible across multiple application instances, though it may impact performance.
    • saveMode - Save mode for the session. Default is SaveMode.ON_SET_ATTRIBUTE.

    • cleanupCron - Specifies the cron expression for scheduling the expired session cleanup task. Default is "0 * * * * *".

    • broadcastSessionUpdates - Specifies whether session updates should be broadcast to other application instances. When enabled, session attribute changes are published to Redis pub/sub channels, allowing other application instances to update their local caches. This ensures session consistency across a distributed deployment. Disable this feature if you have a single application instance or do not require real-time session synchronization across instances, which can reduce Redis network traffic. Default is true.


    Use @EnableLocalCachedRedisWebSession annotation provides the following configuration settings:

    • maxInactiveIntervalInSeconds - Specifies the maximum time, in seconds, that a session can remain idle before it expires. Default is 1800.

    • namespace - Specifies a custom namespace for Redis keys to enable session isolation across multiple applications. The namespace changes the Redis key prefix from the default spring:session: to <redisNamespace>:. This allows multiple applications to share the same Redis instance while maintaining separate session storage. Default is "spring:session".

    • saveMode - Save mode for the session. Default is SaveMode.ON_SET_ATTRIBUTE.

    • broadcastSessionUpdates - Specifies whether session updates should be broadcast to other application instances. When enabled, session attribute changes are published to Redis pub/sub channels, allowing other application instances to update their local caches. This ensures session consistency across a distributed deployment. Disable this feature if you have a single application instance or do not require real-time session synchronization across instances, which can reduce Redis network traffic. Default is true.

Spring Transaction Manager

Redisson provides implementation of both org.springframework.transaction.PlatformTransactionManager and org.springframework.transaction.ReactiveTransactionManager interfaces to participant in Spring transactions. See also Transactions section.

Add redisson-spring-transaction dependency into your project:

  • Community Edition

    Maven

    <dependency>
       <groupId>org.redisson</groupId>
       <artifactId>redisson-spring-transaction</artifactId>
       <version>4.7.0</version>
    </dependency>
    

    Gradle

    compile 'org.redisson:redisson-spring-transaction:4.7.0'
    
  • Redisson PRO

    Maven

    <dependency>
       <groupId>pro.redisson</groupId>
       <artifactId>redisson-spring-transaction</artifactId>
       <version>4.7.0</version>
    </dependency>
    

    Gradle

    compile 'pro.redisson:redisson-spring-transaction:4.7.0'
    

Redisson PRO vs. Community Edition ➜

Spring Transaction Management

@Configuration
@EnableTransactionManagement
public class RedissonTransactionContextConfig {

    @Bean
    public TransactionalBean transactionBean() {
        return new TransactionalBean();
    }

    @Bean
    public RedissonTransactionManager transactionManager(RedissonClient redisson) {
        return new RedissonTransactionManager(redisson);
    }

    @Bean(destroyMethod="shutdown")
    public RedissonClient redisson(@Value("classpath:/redisson.yaml") Resource configFile) throws IOException {
         Config config = Config.fromYAML(configFile.getInputStream());
        return Redisson.create(config);
    }

}


public class TransactionalBean {

    @Autowired
    private RedissonTransactionManager transactionManager;

    @Transactional
    public void commitData() {
        RTransaction transaction = transactionManager.getCurrentTransaction();
        RMap<String, String> map = transaction.getMap("test1");
        map.put("1", "2");
    }

}

Reactive Spring Transaction Management

@Configuration
@EnableTransactionManagement
public class RedissonReactiveTransactionContextConfig {

    @Bean
    public TransactionalBean transactionBean() {
        return new TransactionalBean();
    }

    @Bean
    public ReactiveRedissonTransactionManager transactionManager(RedissonReactiveClient redisson) {
        return new ReactiveRedissonTransactionManager(redisson);
    }

    @Bean(destroyMethod="shutdown")
    public RedissonReactiveClient redisson(@Value("classpath:/redisson.yaml") Resource configFile) throws IOException {
         Config config = Config.fromYAML(configFile.getInputStream());
        return Redisson.createReactive(config);
    }

}

public class TransactionalBean {

    @Autowired
    private ReactiveRedissonTransactionManager transactionManager;

    @Transactional
    public Mono<Void> commitData() {
        Mono<RTransactionReactive> transaction = transactionManager.getCurrentTransaction();
        return transaction.flatMap(t -> {
            RMapReactive<String, String> map = t.getMap("test1");
            return map.put("1", "2");
        }).then();
    }

}

Spring Cloud Stream

This feature is available only in Redisson PRO edition.

Redisson implements Spring Cloud Stream Binder based on Valkey or Redis using the Reliable Queue for messages delivery.

Compatible with Spring versions below.

Spring Cloud Stream Spring Cloud Spring Boot
5.0.x 2025.1.x 4.0.x
4.3.x 2025.0.x 3.5.x
4.2.x 2024.0.x 3.4.x
4.1.x 2023.0.x 3.0.x - 3.3.x
4.0.x 2022.0.x 3.0.x - 3.3.x
3.2.x 2021.0.x 2.6.x, 2.7.x (Starting with 2021.0.3 of Spring Cloud)
3.1.x 2020.0.x 2.4.x, 2.5.x (Starting with 2020.0.3 of Spring Cloud)

To use binder with Redisson you need to add Spring Cloud Stream Binder library in classpath:

Maven:

<dependency>
    <groupId>pro.redisson</groupId>
    <artifactId>spring-cloud-stream-binder-redisson</artifactId>
    <version>4.7.0</version>
</dependency>
Gradle:
compile 'pro.redisson:spring-cloud-stream-binder-redisson:4.7.0'  

Receiving messages

Register the input binder (an event sink) for receiving messages as follows:

@Bean
public Consumer<MyObject> receiveMessage() {
  return obj -> {
     // consume received object ...
  };
}

Define channel id in the configuration file application.properties.

Consumer settings:

  • pollBatchSize - Sets the maximum number of messages to retrieve in a single poll operation. Default value is 10.

  • visibilityTimeout - Sets the visibility timeout for retrieved messages. The time period during which a message is invisible to other consumers after being retrieved. This prevents duplicate processing and allows the message to reappear in the queue if it wasn't acknowledged during that timeout. Default value is 30 seconds.

  • negativeAcknowledgeDelay - Specifies the delay duration before a message handled with an exception is eligible for redelivery. Default value is 15 seconds.

Example for receiveMessage bean defined above connected to my-channel channel:

spring.cloud.stream.bindings.receiveMessage-in-0.destination=my-channel
spring.cloud.stream.redisson.bindings.receiveMessage-in-0.consumer.pollBatchSize=15
spring.cloud.stream.redisson.bindings.receiveMessage-in-0.consumer.visibilityTimeout=60s

YAML configuration:

spring:
  cloud:
    stream:
      bindings:
        receiveMessage-in-0:
          destination: my-channel
      redisson:
        bindings:
          receiveMessage-in-0:
            consumer:
              pollBatchSize: 15
              visibilityTimeout: 60s

Publishing messages

  • Publish messages using an output binder

    Register the output binder (an event source) for publishing messages as follows:

    @Bean
    public Supplier<MyObject> feedSupplier() {
        return () -> {
               // ...
               return new MyObject();
        };
    }
    
  • Publish messages using org.springframework.cloud.stream.function.StreamBridge object

    StreamBridge bridge;
    
    bridge.send("feedSupplier-out-0", new MyObject());
    

Define channel id in the configuration file application.properties. Example for feedSupplier bean defined above connected to my-channel channel:

spring.cloud.stream.bindings.feedSupplier-out-0.destination=my-channel
spring.cloud.stream.bindings.feedSupplier-out-0.producer.useNativeEncoding=true

YAML configuration:

spring:
  cloud:
    stream:
      bindings:
        feedSupplier-out-0:
          destination: my-channel
          producer:
            useNativeEncoding: true

Spring Data Redis

Redisson implements RedisConnectionFactory and ReactiveRedisConnectionFactory interfaces from Spring Data Redis module, allowing usage of RedisTemplate, ReactiveRedisTemplate or ReactiveRedisOperations objects.

Usage

  1. Add redisson-spring-data dependency into your project:

    • Community Edition

      Maven

      <dependency>
          <groupId>org.redisson</groupId>
          <!-- for Spring Data Redis v.1.6.x -->
          <artifactId>redisson-spring-data-16</artifactId>
          <!-- for Spring Data Redis v.1.7.x -->
          <artifactId>redisson-spring-data-17</artifactId>
          <!-- for Spring Data Redis v.1.8.x -->
          <artifactId>redisson-spring-data-18</artifactId>
          <!-- for Spring Data Redis v.2.0.x -->
          <artifactId>redisson-spring-data-20</artifactId>
          <!-- for Spring Data Redis v.2.1.x -->
          <artifactId>redisson-spring-data-21</artifactId>
          <!-- for Spring Data Redis v.2.2.x -->
          <artifactId>redisson-spring-data-22</artifactId>
          <!-- for Spring Data Redis v.2.3.x -->
          <artifactId>redisson-spring-data-23</artifactId>
          <!-- for Spring Data Redis v.2.4.x -->
          <artifactId>redisson-spring-data-24</artifactId>
          <!-- for Spring Data Redis v.2.5.x -->
          <artifactId>redisson-spring-data-25</artifactId>
          <!-- for Spring Data Redis v.2.6.x -->
          <artifactId>redisson-spring-data-26</artifactId>
          <!-- for Spring Data Redis v.2.7.x -->
          <artifactId>redisson-spring-data-27</artifactId>
          <!-- for Spring Data Redis v.3.0.x -->
          <artifactId>redisson-spring-data-30</artifactId>
          <!-- for Spring Data Redis v.3.1.x -->
          <artifactId>redisson-spring-data-31</artifactId>
          <!-- for Spring Data Redis v.3.2.x -->
          <artifactId>redisson-spring-data-32</artifactId>
          <!-- for Spring Data Redis v.3.3.x -->
          <artifactId>redisson-spring-data-33</artifactId>
          <!-- for Spring Data Redis v.3.4.x -->
          <artifactId>redisson-spring-data-34</artifactId>
          <!-- for Spring Data Redis v.3.5.x -->
          <artifactId>redisson-spring-data-35</artifactId>
          <!-- for Spring Data Redis v.4.0.x -->
          <artifactId>redisson-spring-data-40</artifactId>
          <!-- for Spring Data Redis v.4.0.x -->
          <artifactId>redisson-spring-data-41</artifactId>
          <version>4.7.0</version>
      </dependency>
      

      Gradle

      // for Spring Data Redis v.1.6.x
      compile 'org.redisson:redisson-spring-data-16:4.7.0'
      // for Spring Data Redis v.1.7.x
      compile 'org.redisson:redisson-spring-data-17:4.7.0'
      // for Spring Data Redis v.1.8.x
      compile 'org.redisson:redisson-spring-data-18:4.7.0'
      // for Spring Data Redis v.2.0.x
      compile 'org.redisson:redisson-spring-data-20:4.7.0'
      // for Spring Data Redis v.2.1.x
      compile 'org.redisson:redisson-spring-data-21:4.7.0'
      // for Spring Data Redis v.2.2.x
      compile 'org.redisson:redisson-spring-data-22:4.7.0'
      // for Spring Data Redis v.2.3.x
      compile 'org.redisson:redisson-spring-data-23:4.7.0'
      // for Spring Data Redis v.2.4.x
      compile 'org.redisson:redisson-spring-data-24:4.7.0'
      // for Spring Data Redis v.2.5.x
      compile 'org.redisson:redisson-spring-data-25:4.7.0'
      // for Spring Data Redis v.2.6.x
      compile 'org.redisson:redisson-spring-data-26:4.7.0'
      // for Spring Data Redis v.2.7.x
      compile 'org.redisson:redisson-spring-data-27:4.7.0'
      // for Spring Data Redis v.3.0.x
      compile 'org.redisson:redisson-spring-data-30:4.7.0'
      // for Spring Data Redis v.3.1.x
      compile 'org.redisson:redisson-spring-data-31:4.7.0'
      // for Spring Data Redis v.3.2.x
      compile 'org.redisson:redisson-spring-data-32:4.7.0'
      // for Spring Data Redis v.3.3.x
      compile 'org.redisson:redisson-spring-data-33:4.7.0'
      // for Spring Data Redis v.3.4.x
      compile 'org.redisson:redisson-spring-data-34:4.7.0'
      // for Spring Data Redis v.3.5.x
      compile 'org.redisson:redisson-spring-data-35:4.7.0'
      // for Spring Data Redis v.4.0.x
      compile 'org.redisson:redisson-spring-data-40:4.7.0'
      // for Spring Data Redis v.4.1.x
      compile 'org.redisson:redisson-spring-data-40:4.7.0'
      
    • Redisson PRO

      Maven

      <dependency>
          <groupId>pro.redisson</groupId>
          <!-- for Spring Data Redis v.1.6.x -->
          <artifactId>redisson-spring-data-16</artifactId>
          <!-- for Spring Data Redis v.1.7.x -->
          <artifactId>redisson-spring-data-17</artifactId>
          <!-- for Spring Data Redis v.1.8.x -->
          <artifactId>redisson-spring-data-18</artifactId>
          <!-- for Spring Data Redis v.2.0.x -->
          <artifactId>redisson-spring-data-20</artifactId>
          <!-- for Spring Data Redis v.2.1.x -->
          <artifactId>redisson-spring-data-21</artifactId>
          <!-- for Spring Data Redis v.2.2.x -->
          <artifactId>redisson-spring-data-22</artifactId>
          <!-- for Spring Data Redis v.2.3.x -->
          <artifactId>redisson-spring-data-23</artifactId>
          <!-- for Spring Data Redis v.2.4.x -->
          <artifactId>redisson-spring-data-24</artifactId>
          <!-- for Spring Data Redis v.2.5.x -->
          <artifactId>redisson-spring-data-25</artifactId>
          <!-- for Spring Data Redis v.2.6.x -->
          <artifactId>redisson-spring-data-26</artifactId>
          <!-- for Spring Data Redis v.2.7.x -->
          <artifactId>redisson-spring-data-27</artifactId>
          <!-- for Spring Data Redis v.3.0.x -->
          <artifactId>redisson-spring-data-30</artifactId>
          <!-- for Spring Data Redis v.3.1.x -->
          <artifactId>redisson-spring-data-31</artifactId>
          <!-- for Spring Data Redis v.3.2.x -->
          <artifactId>redisson-spring-data-32</artifactId>
          <!-- for Spring Data Redis v.3.3.x -->
          <artifactId>redisson-spring-data-33</artifactId>
          <!-- for Spring Data Redis v.3.4.x -->
          <artifactId>redisson-spring-data-34</artifactId>
          <!-- for Spring Data Redis v.3.5.x -->
          <artifactId>redisson-spring-data-35</artifactId>
          <!-- for Spring Data Redis v.4.0.x -->
          <artifactId>redisson-spring-data-40</artifactId>
          <!-- for Spring Data Redis v.4.1.x -->
          <artifactId>redisson-spring-data-41</artifactId>
          <version>4.7.0</version>
      </dependency>
      

      Gradle

      // for Spring Data Redis v.1.6.x
      compile 'pro.redisson:redisson-spring-data-16:4.7.0'
      // for Spring Data Redis v.1.7.x
      compile 'pro.redisson:redisson-spring-data-17:4.7.0'
      // for Spring Data Redis v.1.8.x
      compile 'pro.redisson:redisson-spring-data-18:4.7.0'
      // for Spring Data Redis v.2.0.x
      compile 'pro.redisson:redisson-spring-data-20:4.7.0'
      // for Spring Data Redis v.2.1.x
      compile 'pro.redisson:redisson-spring-data-21:4.7.0'
      // for Spring Data Redis v.2.2.x
      compile 'pro.redisson:redisson-spring-data-22:4.7.0'
      // for Spring Data Redis v.2.3.x
      compile 'pro.redisson:redisson-spring-data-23:4.7.0'
      // for Spring Data Redis v.2.4.x
      compile 'pro.redisson:redisson-spring-data-24:4.7.0'
      // for Spring Data Redis v.2.5.x
      compile 'pro.redisson:redisson-spring-data-25:4.7.0'
      // for Spring Data Redis v.2.6.x
      compile 'pro.redisson:redisson-spring-data-26:4.7.0'
      // for Spring Data Redis v.2.7.x
      compile 'pro.redisson:redisson-spring-data-27:4.7.0'
      // for Spring Data Redis v.3.0.x
      compile 'pro.redisson:redisson-spring-data-30:4.7.0'
      // for Spring Data Redis v.3.1.x
      compile 'pro.redisson:redisson-spring-data-31:4.7.0'
      // for Spring Data Redis v.3.2.x
      compile 'pro.redisson:redisson-spring-data-32:4.7.0'
      // for Spring Data Redis v.3.3.x
      compile 'pro.redisson:redisson-spring-data-33:4.7.0'
      // for Spring Data Redis v.3.4.x
      compile 'pro.redisson:redisson-spring-data-34:4.7.0'
      // for Spring Data Redis v.3.5.x
      compile 'pro.redisson:redisson-spring-data-35:4.7.0'
      // for Spring Data Redis v.4.0.x
      compile 'pro.redisson:redisson-spring-data-40:4.7.0'
      // for Spring Data Redis v.4.1.x
      compile 'pro.redisson:redisson-spring-data-41:4.7.0'
      

      License key configuration

    Redisson PRO vs. Community Edition ➜

  2. Register RedissonConnectionFactory in Spring context:

    @Configuration
    public class RedissonSpringDataConfig {
    
       @Bean
       public RedissonConnectionFactory redissonConnectionFactory(RedissonClient redisson) {
           return new RedissonConnectionFactory(redisson);
       }
    
       @Bean(destroyMethod = "shutdown")
       public RedissonClient redisson(@Value("classpath:/redisson.yaml") Resource configFile) throws IOException {
           Config config = Config.fromYAML(configFile.getInputStream());
           return Redisson.create(config);
       }
    
    }
    

Spring AI Vector Store

This feature is available only in Redisson PRO edition.

Redisson provides Spring AI Vector Store implementation for building AI-powered applications. It supports a wide range of use cases including Retrieval Augmented Generation (RAG), semantic search, document similarity and recommendations, and memory for AI agents. The implementation uses the Redis Query Engine to store and query vector embeddings.

The store uses Redis JSON documents to persist vector embeddings along with their associated document content and metadata, and creates a vector similarity index over them.

Features:

  • Vector similarity search using KNN (K-Nearest Neighbors)
  • Support for HNSW and FLAT vector indexing algorithms
  • Multiple distance metrics: COSINE, L2 (Euclidean), Inner Product
  • Configurable metadata fields (TEXT, TAG, NUMERIC) for advanced filtering
  • Automatic schema initialization
  • Portable filter expressions
  • Batch processing support
  • Observability through Micrometer
  • Semantic search over natural language corpora
  • Document similarity and content-based recommendations
  • Persistent memory for AI agents and multi-turn conversations

Supported Spring AI versions

Two sets of artifacts are published, one per Spring AI generation. They are identical in features and differ only in the Spring AI API they compile against, so pick the one matching the Spring AI version already on the classpath.

Spring AI Artifact suffix Store artifact Starter artifact
1.0.x -10 redisson-spring-ai-store-10 redisson-spring-ai-store-starter-10
2.0.x -20 redisson-spring-ai-store-20 redisson-spring-ai-store-starter-20

Spring AI 2.0 additionally ships two features which have no 1.0 counterpart:

Feature Store artifact Starter artifact
Chat Memory redisson-spring-ai-chat-20 redisson-spring-ai-chat-starter-20
Semantic Cache redisson-spring-ai-semantic-cache-20 redisson-spring-ai-semantic-cache-starter-20

Prerequisites

  1. Redis 8.0 or higher. The Redis Query Engine and JSON are part of Redis itself from Redis 8, so the standard redis distribution and Docker image carry everything the store needs and no module has to be loaded. For Redis 7.x and earlier, use Redis Stack, which bundles the RediSearch and RedisJSON modules.

  2. EmbeddingModel instance to compute the document embeddings. Several options are available:

Usage

1. Add dependency into your project

Replace the -20 suffix with -10 if the project uses Spring AI 1.0.x.

Spring Boot Starter (recommended)

For Spring Boot applications with auto-configuration support:

Maven

<dependency>
    <groupId>pro.redisson</groupId>
    <artifactId>redisson-spring-ai-store-starter-20</artifactId>
    <version>xVERSIONx</version>
</dependency>

Gradle

compile 'pro.redisson:redisson-spring-ai-store-starter-20:xVERSIONx'

Store Implementation Only

For manual configuration or non-Spring Boot applications:

Maven

<dependency>
    <groupId>pro.redisson</groupId>
    <artifactId>redisson-spring-ai-store-20</artifactId>
    <version>xVERSIONx</version>
</dependency>

Gradle

compile 'pro.redisson:redisson-spring-ai-store-20:xVERSIONx'

License key configuration

2. Add settings into application.yaml file

spring:
  ai:
    vectorstore:
      redisson:
        index-name: my-index
        prefix: doc:
        initialize-schema: true
        vector-algorithm: HNSW
        distance-metric: COSINE
        hnsw:
          m: 16
          ef-construction: 200
          ef-runtime: 10
        metadata-fields:
          - name: category
            type: TAG
          - name: year
            type: NUMERIC

3. Use VectorStore in your application

@Autowired 
VectorStore vectorStore;

// ...

List<Document> documents = List.of(
    new Document("Spring AI rocks!! Spring AI rocks!!", Map.of("category", "framework", "year", 2024)),
    new Document("The World is Big and Salvation Lurks Around the Corner"),
    new Document("You walk forward facing the past and you turn back toward the future.", Map.of("category", "philosophy", "year", 2023)));

// Add the documents to Redis
vectorStore.add(documents);

// Retrieve documents similar to a query
List<Document> results = vectorStore.similaritySearch(
    SearchRequest.builder()
        .query("Spring")
        .topK(5)
        .build());

Configuration

Properties starting with spring.ai.vectorstore.redisson.* are used to configure the Vector Store:

Property Description Default Value
spring.ai.vectorstore.redisson.index-name Name of the Redis search index spring-ai-index
spring.ai.vectorstore.redisson.prefix Prefix for Redis keys embedding:
spring.ai.vectorstore.redisson.initialize-schema Whether to initialize the required schema false
spring.ai.vectorstore.redisson.vector-algorithm Vector indexing algorithm (HNSW or FLAT) HNSW
spring.ai.vectorstore.redisson.distance-metric Distance metric (COSINE, L2, or IP) COSINE

The initialize-schema property must be set to true for automatic index creation. It is false by default so that an application cannot silently create an index against a production database; create the index up front and leave the property unset if schema changes are managed outside the application.

When it is enabled the index is created on startup only if it is absent. Changing a metadata field's type, the vector algorithm or the distance metric afterwards has no effect on an index that already exists - drop the index and let it be recreated, or rebuild it manually.

Vector Algorithms

  • HNSW

    Hierarchical Navigable Small World - default algorithm that provides better search performance with slightly higher memory usage. Recommended for most use cases.

    • m - Controls the number of bi-directional links created for each node. Higher values improve recall but increase memory usage. Recommended range: 12-48.
    • ef-construction - Determines search width during index building. Higher values create higher quality indexes at the cost of longer construction time. Should be at least 2 * m. Recommended range: 100-500.
    • ef-runtime - Controls search precision at query time. Higher values improve recall but increase query latency. Recommended range: 10-100.
  • FLAT

    Brute force algorithm that provides exact results but slower performance for large datasets. Use when exact accuracy is required and dataset size is small.

HNSW Algorithm Parameters:

Property Description Default Value
spring.ai.vectorstore.redisson.hnsw.m Maximum number of connections per node 16
spring.ai.vectorstore.redisson.hnsw.ef-construction Search width during index building 200
spring.ai.vectorstore.redisson.hnsw.ef-runtime Search width during query execution 10

Metadata fields

Metadata fields enable filtering capabilities during similarity searches. You must explicitly define all metadata field names and types for any metadata field used in filter expressions.

Metadata Field Types:

Type Description Use Case
TAG Exact match filtering Categorical data, labels, status values
TEXT Full-text search Descriptions, content fields
NUMERIC Range queries Years, prices, counts, scores

Configuration example:

spring:
  ai:
    vectorstore:
      redisson:
        index-name: "my-index"
        prefix: "doc:"
        initialize-schema: true
        vector-algorithm: HNSW
        distance-metric: COSINE
        hnsw:
          m: 16
          ef-construction: 200
          ef-runtime: 10      
        metadata-fields:
          - name: category
            type: TAG
          - name: description
            type: TEXT
          - name: year
            type: NUMERIC
          - name: price
            type: NUMERIC

Metadata Filtering

You can use the generic metadata filters with Redisson Vector Store.

Using text expression language:

vectorStore.similaritySearch(SearchRequest.builder()
        .query("The World")
        .topK(5)
        .similarityThreshold(0.7)
        .filterExpression("country in ['UK', 'NL'] && year >= 2020")
        .build());

Using programmatic Filter.Expression DSL:

FilterExpressionBuilder b = new FilterExpressionBuilder();

vectorStore.similaritySearch(SearchRequest.builder()
        .query("The World")
        .topK(5)
        .similarityThreshold(0.7)
        .filterExpression(b.and(
                b.in("country", "UK", "NL"),
                b.gte("year", 2020)).build())
        .build());

Filter expressions are automatically converted into Redis search queries:

Portable expression Redis query
country == 'BG' @country:{BG}
genre == 'drama' && year >= 2020 @genre:{drama} @year:[2020 inf]
genre in ['comedy', 'documentary', 'drama'] @genre:{comedy \| documentary \| drama}
year >= 2020 \|\| (country == 'BG' && city != 'Sofia') @year:[2020 inf] \| (@country:{BG} -@city:{Sofia})
(year >= 2020 \|\| country == 'BG') && city nin ['Sofia', 'Plovdiv'] (@year:[2020 inf] \| @country:{BG}) -@city:{Sofia \| Plovdiv}
temperature >= -15.6 && temperature <= 20.13 @temperature:[-15.6 inf] @temperature:[-inf 20.13]

A field's declared type decides the syntax it converts to, so a field filtered as a range has to be declared NUMERIC and a field filtered by exact match TAG. A field that is not declared at all is treated as TAG, which silently turns a range filter over an undeclared field into something that matches nothing.

A key containing spaces is quoted, in either single or double quotes: "country 1 2 3" == 'BG' becomes @"country 1 2 3":{BG}.

Distance Metrics

The Vector Store supports three distance metrics:

Metric Description Best For
COSINE Cosine similarity (default) Text embeddings, semantic similarity
L2 Euclidean distance Image embeddings, spatial data
IP Inner Product Pre-normalized embeddings

Each metric is automatically normalized to a 0-1 similarity score, where 1 indicates maximum similarity. Document.getScore() reports that similarity, so a higher value is a closer match, and similarityThreshold on a SearchRequest is compared against it.

Stored document layout

Each document is stored as a JSON object under <prefix><document id> with four fields:

Field Holds
content the document text, indexed as TEXT
embedding the vector, indexed as HNSW or FLAT
metadata the document metadata; only the fields declared in metadata-fields are indexed

Metadata that is not declared in metadata-fields is still stored and still returned with the document. It simply cannot be filtered on.

A document returned by similaritySearch carries two metadata entries it was not stored with - the raw distance under RedissonVectorStore.DISTANCE_FIELD_NAME (vector_score) and the same value under Spring AI's own DocumentMetadata.DISTANCE key (distance). A document stored with one metadata field therefore comes back holding three.

Adding a document with an id that already exists replaces it, content, embedding and metadata alike, so add is an upsert rather than an append:

String id = UUID.randomUUID().toString();
vectorStore.add(List.of(new Document(id, "Spring AI rocks!!", Map.of("meta1", "meta1"))));

// same id - replaces the document above rather than adding a second one
vectorStore.add(List.of(new Document(id, "The World is Big", Map.of("meta2", "meta2"))));

Deleting

Documents are removed by id, or by the same filter expressions searching uses:

vectorStore.delete(List.of(id1, id2));

vectorStore.delete("type == 'A' && priority > 1");

vectorStore.delete(new FilterExpressionBuilder()
        .and(b.eq("type", "A"), b.gt("priority", 1))
        .build());

Observability

The store extends Spring AI's AbstractObservationVectorStore, so add, delete and similaritySearch are recorded through Micrometer whenever an ObservationRegistry is present. In a Spring Boot application the registry is injected automatically; with manual configuration pass it to the builder.

Observations are published under Spring AI's standard vector store names, with the provider reported as redis:

Key Value
db.system redis
db.operation.name add, delete or query
spring.ai.kind vector_store
db.collection.name the index name
db.vector.field.name embedding
db.vector.dimension_count the embedding model's dimensions
db.search.similarity_metric the configured distance metric
db.vector.query.content, db.vector.query.top_k, db.vector.query.similarity_threshold the query, on query observations only

Batching

Embeddings are computed in batches sized by a BatchingStrategy, defaulting to TokenCountBatchingStrategy, which keeps each request to the embedding model under its token limit. Supply a different strategy through the builder when the model has an unusual limit.

RAG Integration Example

Combine the vector store with Spring AI's ChatClient for Retrieval Augmented Generation:

@Service
public class RagService {

    private final ChatClient chatClient;
    private final VectorStore vectorStore;

    public RagService(ChatClient.Builder chatClientBuilder, VectorStore vectorStore) {
        this.chatClient = chatClientBuilder.build();
        this.vectorStore = vectorStore;
    }

    public String askQuestion(String question) {
        // Retrieve relevant documents
        List<Document> relevantDocs = vectorStore.similaritySearch(
            SearchRequest.builder()
                .query(question)
                .topK(3)
                .similarityThreshold(0.7)
                .build()
        );

        // Build context from retrieved documents
        String context = relevantDocs.stream()
            .map(Document::getText)
            .collect(Collectors.joining("\n\n"));

        // Generate response with context
        return chatClient.prompt()
            .user(u -> u.text("""
                Based on the following context, answer the question.

                Context:
                {context}

                Question: {question}
                """)
                .param("context", context)
                .param("question", question))
            .call()
            .content();
    }
}
A repeated question costs a model call every time it is asked. Spring AI Semantic Cache answers it from a previous response when the two questions mean the same thing.

Semantic Search Example

Unlike keyword search, semantic search finds results based on meaning rather than exact word matches. This is useful for documentation search, support knowledge bases, product catalogs, or any corpus where users may phrase queries in unexpected ways.

Configure metadata fields for filtering in application.yaml:

spring:
  ai:
    vectorstore:
      redisson:
        index-name: "semantic-search-index"
        prefix: "doc:"
        initialize-schema: true
        metadata-fields:
          - name: source
            type: TAG
          - name: category
            type: TAG

Index documents and run semantic queries:

@Service
public class SemanticSearchService {

    private final VectorStore vectorStore;

    public SemanticSearchService(VectorStore vectorStore) {
        this.vectorStore = vectorStore;
    }

    // Index documents
    public void indexDocuments(List<String> texts, String source) {
        List<Document> documents = texts.stream()
            .map(text -> new Document(text, Map.of("source", source)))
            .toList();

        vectorStore.add(documents);
    }

    // Search by natural language query
    public List<Document> search(String query, int topK) {
        return vectorStore.similaritySearch(
            SearchRequest.builder()
                .query(query)
                .topK(topK)
                .similarityThreshold(0.7)
                .build());
    }

    // Search scoped to a specific source with metadata filtering
    public List<Document> searchBySource(String query, String source) {
        return vectorStore.similaritySearch(
            SearchRequest.builder()
                .query(query)
                .topK(5)
                .similarityThreshold(0.7)
                .filterExpression("source == '" + source + "'")
                .build());
    }
}

Usage example:

// Index documents from a known source
searchService.indexDocuments(List.of(
    "Spring Boot simplifies Java application development",
    "Docker containers enable consistent deployments",
    "Kubernetes orchestrates containerized workloads",
    "PostgreSQL is a powerful open-source relational database"
), "knowledge-base");

// Search across all indexed documents — returns results about containers and orchestration
List<Document> results = searchService.search("how do I deploy my app reliably", 5);

// Search scoped to a specific source only
List<Document> scoped = searchService.searchBySource("relational database options", "knowledge-base");

Document Similarity & Recommendations Example

Vector embeddings can power content-based recommendation engines — surfacing articles, products, or documents that are semantically close to a given item or to a user's recent activity.

Configure metadata fields in application.yaml:

spring:
  ai:
    vectorstore:
      redisson:
        index-name: "recommendations-index"
        prefix: "article:"
        initialize-schema: true
        metadata-fields:
          - name: articleId
            type: TAG
          - name: title
            type: TEXT
          - name: category
            type: TAG

Store articles and retrieve recommendations:

@Service
public class RecommendationService {

    private final VectorStore vectorStore;

    public RecommendationService(VectorStore vectorStore) {
        this.vectorStore = vectorStore;
    }

    // Store an article with metadata
    public void addArticle(String id, String title, String content, String category) {
        vectorStore.add(List.of(new Document(
            content,
            Map.of("articleId", id, "title", title, "category", category)
        )));
    }

    // Find articles similar to a given article's content
    public List<Map<String, Object>> findSimilarArticles(String articleContent, int topK) {
        return vectorStore.similaritySearch(
                SearchRequest.builder()
                    .query(articleContent)
                    .topK(topK + 1) // +1 to account for the article itself
                    .similarityThreshold(0.75)
                    .build())
            .stream()
            .map(doc -> Map.of(
                "id",       doc.getMetadata().get("articleId"),
                "title",    doc.getMetadata().get("title"),
                "category", doc.getMetadata().get("category"),
                "score",    doc.getScore()
            ))
            .toList();
    }

    // Recommend articles based on a user's reading history
    public List<Map<String, Object>> recommendFromHistory(List<String> readArticleContents) {
        // Combine recent reading history into a single semantic query
        String combinedContext = String.join(" ", readArticleContents);

        return vectorStore.similaritySearch(
                SearchRequest.builder()
                    .query(combinedContext)
                    .topK(10)
                    .similarityThreshold(0.65)
                    .build())
            .stream()
            .map(doc -> Map.of(
                "id",       doc.getMetadata().get("articleId"),
                "title",    doc.getMetadata().get("title"),
                "category", doc.getMetadata().get("category"),
                "score",    doc.getScore()
            ))
            .toList();
    }
}

Usage example:

// Index some articles
recommendationService.addArticle("1", "Intro to Spring AI",      "Spring AI intro content...",    "AI");
recommendationService.addArticle("2", "LangChain vs Spring AI",  "Comparison article content...", "AI");
recommendationService.addArticle("3", "Docker Best Practices",   "Container content...",          "DevOps");

// Get articles similar to article 1 — will surface article 2 as highly similar
List<Map<String, Object>> similar = recommendationService.findSimilarArticles("Spring AI intro content...", 3);

// Recommend based on a user who recently read articles 1 and 2
List<Map<String, Object>> recommended = recommendationService.recommendFromHistory(
    List.of("Spring AI intro content...", "Comparison article content..."));

Memory for AI Agents Example

Agents and multi-turn chat applications can use the vector store as a long-term memory layer, persisting conversation turns and facts as embeddings and retrieving the most contextually relevant ones at each step.

This is semantic recall - the memories most relevant to what the user just said, whenever they were stored. For the transcript of a conversation in the order it happened, use Spring AI Chat Memory, which implements Spring AI's own ChatMemoryRepository. The two are complementary: the repository holds the recent turns, the vector store holds what is worth remembering from all of them.

Configure metadata fields in application.yaml:

spring:
  ai:
    vectorstore:
      redisson:
        index-name: "agent-memory-index"
        prefix: "memory:"
        initialize-schema: true
        metadata-fields:
          - name: sessionId
            type: TAG
          - name: type
            type: TAG
          - name: timestamp
            type: TEXT

Store and recall memories, then use them to augment chat responses:

@Service
public class AgentMemoryService {

    private final VectorStore vectorStore;
    private final ChatClient chatClient;

    public AgentMemoryService(VectorStore vectorStore, ChatClient.Builder chatClientBuilder) {
        this.vectorStore = vectorStore;
        this.chatClient = chatClientBuilder.build();
    }

    // Persist a memory (conversation turn, fact, or observation)
    public void remember(String sessionId, String memoryText, String type) {
        vectorStore.add(List.of(new Document(
            memoryText,
            Map.of(
                "sessionId", sessionId,
                "type",      type,          // "conversation", "fact", "preference"
                "timestamp", Instant.now().toString()
            )
        )));
    }

    // Recall memories relevant to the current input, scoped to a session
    public List<Document> recall(String sessionId, String currentInput, int topK) {
        return vectorStore.similaritySearch(
            SearchRequest.builder()
                .query(currentInput)
                .topK(topK)
                .similarityThreshold(0.6)
                .filterExpression("sessionId == '" + sessionId + "'")
                .build());
    }

    // Chat with memory-augmented context
    public String chat(String sessionId, String userMessage) {
        // 1. Recall relevant past memories for this session
        List<Document> memories = recall(sessionId, userMessage, 5);

        String memoryContext = memories.isEmpty()
            ? "No relevant memories."
            : memories.stream()
                .map(Document::getText)
                .collect(Collectors.joining("\n- ", "- ", ""));

        // 2. Build a system prompt that injects the recalled context
        String systemPrompt = """
            You are a helpful assistant with memory of past interactions.

            Relevant context from memory:
            %s

            Use this context to personalize your response where appropriate.
            """.formatted(memoryContext);

        // 3. Call the LLM with the enriched prompt
        String response = chatClient.prompt()
            .system(systemPrompt)
            .user(userMessage)
            .call()
            .content();

        // 4. Persist this interaction for future recall
        remember(sessionId, "User asked: " + userMessage, "conversation");
        remember(sessionId, "Assistant responded: " + response, "conversation");

        return response;
    }
}

Usage example:

// Teach the agent facts about a user
memoryService.remember("user-42", "User prefers concise answers", "fact");
memoryService.remember("user-42", "User is a Java developer", "fact");
memoryService.remember("user-42", "User dislikes verbose responses", "preference");

// Start a conversation — relevant facts and past turns are recalled automatically
String reply1 = memoryService.chat("user-42", "What is Spring AI?");

// Follow-up turn — the agent remembers the previous exchange and user preferences
String reply2 = memoryService.chat("user-42", "How do I add a vector store to it?");

// Recall only facts stored for a user, filtered by type
List<Document> facts = memoryService.recall("user-42", "communication style", 5)
    .stream()
    .filter(doc -> "fact".equals(doc.getMetadata().get("type")))
    .toList();

Spring AI Chat Memory

This feature is available only in Redisson PRO edition.

Requires Spring AI 2.0.x.

Redisson provides a Spring AI ChatMemoryRepository implementation, which stores the messages of a conversation so that a model can be given the history of an exchange rather than a single turn.

Each message is stored as a Redis JSON document and indexed by the Redis Query Engine, so a conversation can be read back in order, and messages can be searched by content, type, timestamp or metadata across every conversation.

Features:

  • Full ChatMemoryRepository implementation, usable with MessageChatMemoryAdvisor and the rest of Spring AI's chat memory support
  • Every Spring AI message type: user, assistant, system and tool response
  • Tool calls, tool responses and attached media preserved across a round trip
  • Per-message time to live
  • Search by content, message type, timestamp range or metadata
  • Configurable metadata fields (TEXT, TAG, NUMERIC) for filtering
  • Strictly increasing message ordering within a conversation, maintained across concurrent writers

Prerequisites

  1. Redis 8.0 or higher. The Redis Query Engine and JSON are part of Redis itself from Redis 8, so the standard redis distribution and Docker image carry everything the repository needs and no module has to be loaded. For Redis 7.x and earlier, use Redis Stack.

Usage

1. Add dependency into your project

Spring Boot Starter (recommended)

Maven

<dependency>
    <groupId>pro.redisson</groupId>
    <artifactId>redisson-spring-ai-chat-starter-20</artifactId>
    <version>xVERSIONx</version>
</dependency>

Gradle

compile 'pro.redisson:redisson-spring-ai-chat-starter-20:xVERSIONx'

Repository Implementation Only

For manual configuration or non-Spring Boot applications:

Maven

<dependency>
    <groupId>pro.redisson</groupId>
    <artifactId>redisson-spring-ai-chat-20</artifactId>
    <version>xVERSIONx</version>
</dependency>

Gradle

compile 'pro.redisson:redisson-spring-ai-chat-20:xVERSIONx'

License key configuration

2. Use the repository in your application

The repository is a plain ChatMemoryRepository, so it plugs into Spring AI's chat memory the same way any other does:

@Autowired
ChatMemoryRepository chatMemoryRepository;

ChatMemory chatMemory = MessageWindowChatMemory.builder()
        .chatMemoryRepository(chatMemoryRepository)
        .maxMessages(20)
        .build();

String answer = chatClient.prompt()
        .user("What did I ask you first?")
        .advisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
        .advisors(a -> a.param(ChatMemory.CONVERSATION_ID, "user-42"))
        .call()
        .content();

Used directly, through the ChatMemoryRepository contract:

chatMemoryRepository.saveAll("user-42", List.of(
        new UserMessage("What is Redisson?"),
        new AssistantMessage("A Java client for Valkey and Redis.")));

List<Message> history = chatMemoryRepository.findByConversationId("user-42");

List<String> conversations = chatMemoryRepository.findConversationIds();

chatMemoryRepository.deleteByConversationId("user-42");

RedissonChatMemoryRepository adds methods of its own, which append to a conversation instead of replacing it:

RedissonChatMemoryRepository repository = new RedissonChatMemoryRepository(options);

// append one message, or several, keeping what is already there
repository.add("user-42", new UserMessage("What is Redisson?"));
repository.add("user-42", List.of(
        new AssistantMessage("A Java client for Valkey and Redis."),
        new UserMessage("Does it do vector search?")));

// read the conversation back in order
List<Message> all = repository.get("user-42");

// or only the most recent messages
List<Message> recent = repository.get("user-42", 10);

// remove one conversation
repository.clear("user-42");

saveAll replaces a conversation, so appending a turn through it means re-sending the whole history. add is the one to call on a chat path.

Configuration

The repository is configured through RedissonChatMemoryOptions:

@Bean
ChatMemoryRepository chatMemoryRepository(RedissonClient redisson) {
    return new RedissonChatMemoryRepository(
            RedissonChatMemoryOptions.client(redisson)
                    .indexName("chat-memory-idx")
                    .keyPrefix("chat-memory:")
                    .timeToLive(Duration.ofDays(30))
                    .initializeSchema(true)
                    .maxConversationIds(1000)
                    .maxMessagesPerConversation(1000)
                    .metadataFields(List.of(
                            Map.of("name", "priority", "type", "tag"),
                            Map.of("name", "score", "type", "numeric"))));
}
Option Description Default Value
indexName Name of the search index holding the messages chat-memory-idx
keyPrefix Prefix prepended to the key of every stored message chat-memory:
timeToLive How long a stored message lives, counted from when it is written. A negative duration keeps messages forever no expiration
initializeSchema Whether the index is created on startup if it is absent true
maxConversationIds Largest number of conversation ids findConversationIds() returns 1000
maxMessagesPerConversation Largest number of messages returned for one conversation 1000
metadataFields Metadata fields added to the index, which makes them available for filtering empty

Metadata fields

Message metadata is always stored and always returned, but only the fields declared here can be filtered on. Each entry is a map holding a name and a type, following the RedisVL schema format:

Type Description Use Case
tag Exact match filtering Categorical data, labels, status values
text Full-text search Descriptions, free-form content
numeric Range queries Scores, counts, confidence values

When no metadata fields are declared, all metadata is indexed as one full-text field instead, which supports loose matching but no typed filtering.

Message ordering

Messages within a conversation are ordered by a timestamp reserved from a Redis counter rather than taken from the local clock, so ordering holds when several application instances write to the same conversation at once. A batch reserves a block of timestamps in one atomic step, so messages saved together keep the order they were passed in.

Searching across conversations

Beyond the ChatMemoryRepository contract, the repository can search the whole index. Every method returns the message together with the conversation it belongs to and the timestamp it was stored at:

AdvancedRedissonChatMemoryRepository advanced = (AdvancedRedissonChatMemoryRepository) chatMemoryRepository;

// each result is a record carrying the message, its conversation and its timestamp
for (AdvancedRedissonChatMemoryRepository.MessageWithConversation result : advanced.findByType(MessageType.USER, 10)) {
    result.message();
    result.conversationId();
    result.timestamp();
}

// by content, using the query engine's own text syntax, so wildcards work
advanced.findByContent("deploy*", 10);

// by message type
advanced.findByType(MessageType.ASSISTANT, 10);

// by timestamp range, optionally scoped to one conversation
advanced.findByTimeRange("user-42", Instant.now().minus(Duration.ofDays(1)), Instant.now(), 50);

// by a declared metadata field
advanced.findByMetadata("priority", "high", 10);

// or with a raw query
advanced.executeQuery("@type:(ASSISTANT) @conversation_id:{user\\-42}", 10);

Stored document layout

Each message is stored as a JSON document under <keyPrefix><conversation id>:<timestamp>:

Field Holds
type the message type, indexed as TEXT
content the message text, indexed as TEXT
conversation_id the conversation, indexed as TAG
timestamp when the message was stored, indexed as sortable NUMERIC
metadata the message metadata
toolCalls the tool calls of an assistant message, when it has any
toolResponses the responses of a tool message, when it has any
media attached media, with binary content Base64 encoded

Spring AI Semantic Cache

This feature is available only in Redisson PRO edition.

Requires Spring AI 2.0.x.

A semantic cache answers a request from a previous response when the two questions mean the same thing, rather than only when they are spelled the same way. "What is the capital of France?" and "What is France's capital city?" hit the same entry, so the second one costs a vector search instead of a model call.

The cache is built on the Spring AI Vector Store: the question is embedded and stored as the document text, and the response travels in the document body.

Features:

  • Similarity matching through the Redis Query Engine, with a configurable threshold
  • Per-lookup threshold override
  • Per-entry time to live
  • Responses produced under different system prompts kept apart, so changing a prompt does not serve answers produced under the old one
  • Eviction of one entry, of one context, or of everything
  • A ChatClient advisor which caches on the way out and answers on the way in, on both the blocking and the streaming path

Prerequisites

  1. Redis 8.0 or higher. The Redis Query Engine and JSON are part of Redis itself from Redis 8, so the standard redis distribution and Docker image carry everything the cache needs and no module has to be loaded. For Redis 7.x and earlier, use Redis Stack.

  2. EmbeddingModel instance to compute the query embeddings, as for the vector store.

Usage

1. Add dependency into your project

Spring Boot Starter (recommended)

Maven

<dependency>
    <groupId>pro.redisson</groupId>
    <artifactId>redisson-spring-ai-semantic-cache-starter-20</artifactId>
    <version>xVERSIONx</version>
</dependency>

Gradle

compile 'pro.redisson:redisson-spring-ai-semantic-cache-starter-20:xVERSIONx'

Cache Implementation Only

For manual configuration or non-Spring Boot applications:

Maven

<dependency>
    <groupId>pro.redisson</groupId>
    <artifactId>redisson-spring-ai-semantic-cache-20</artifactId>
    <version>xVERSIONx</version>
</dependency>

Gradle

compile 'pro.redisson:redisson-spring-ai-semantic-cache-20:xVERSIONx'

License key configuration

2. Configure the cache

The cache needs a RedissonVectorStore carrying its schema. RedissonSemanticCache.vectorStoreBuilder supplies one already configured, leaving the index name and prefix to set:

@Bean
RedissonVectorStore semanticCacheStore(RedissonClient redisson, EmbeddingModel embeddingModel) {
    return RedissonSemanticCache.vectorStoreBuilder(redisson, embeddingModel)
            .indexName("semantic-cache-index")
            .prefix("semantic-cache:")
            .build();
}

@Bean
SemanticCache semanticCache(RedissonVectorStore semanticCacheStore) {
    return RedissonSemanticCache.builder(semanticCacheStore)
            .similarityThreshold(0.9)
            .build();
}

similarityThreshold is how close a stored question has to be to count as a hit, from 0 to 1, defaulting to 0.8. Raise it to answer only near-identical questions; lower it to answer more broadly and risk returning a response to a question the user did not ask.

3. Add the advisor to your ChatClient

@Bean
SemanticCacheAdvisor semanticCacheAdvisor(SemanticCache cache) {
    return SemanticCacheAdvisor.builder(cache)
            .timeToLive(Duration.ofHours(1))
            .build();
}

String answer = chatClient.prompt()
        .user("What is the capital of France?")
        .advisors(semanticCacheAdvisor)
        .call()
        .content();

The advisor looks the question up before the request reaches the model, and caches whatever the model returns when it does not find one. It works the same way on stream(), where the response is assembled as it arrives and cached once the stream completes.

Advisor option Description Default Value
timeToLive How long a cached response stays valid no expiration
similarityThreshold Overrides the cache's own threshold for requests going through this advisor the cache's threshold
isolateBySystemPrompt Whether responses produced under different system prompts are kept apart true
order The advisor's place in the chain BaseChatMemoryAdvisor default
scheduler The scheduler the streaming path caches on Schedulers.boundedElastic()

A cache failure is not a request failure. If Redis is unreachable the advisor logs it and continues to the model, so an outage makes requests slower rather than failing them.

Context isolation

A response produced under one system prompt is usually wrong under another - the same question answered "as a pirate" and "as a lawyer" are different answers. The advisor derives a hash from the system prompt and stores the response under it, so only a request carrying the same system prompt can be answered from it.

Requests with no system prompt are stored under no context, and a lookup carrying no context sees only those entries - not everything.

Set isolateBySystemPrompt(false) to share responses across system prompts, which is worth doing only when the system prompt has no bearing on the answer.

When a prompt changes, the responses cached under the old one are stale and can be dropped without touching the rest:

cache.evictContext(oldPromptHash);

Using the cache directly

The advisor is a wrapper over an interface which can be used on its own:

// store a response, optionally with a context and a time to live
String id = cache.put(CacheEntry.of("What is the capital of France?", response)
        .withContextHash("assistant-v2")
        .withTimeToLive(Duration.ofHours(1)));

// look one up
Optional<CacheHit> hit = cache.get(CacheQuery.of("What is France's capital city?", "assistant-v2"));

// or with a stricter threshold than the cache's own, for this lookup only
Optional<CacheHit> exact = cache.get(CacheQuery.of("What is the capital of France?")
        .withSimilarityThreshold(0.98));

// remove one entry, one context, or everything
cache.evict(id);
cache.evictContext("assistant-v2");
long removed = cache.clear();

A hit reports more than the response:

hit.ifPresent(h -> {
    h.response();      // the cached ChatResponse
    h.query();         // the question it was stored under, which is not the one asked
    h.score();         // how similar the two are, from 0 to 1
    h.id();            // what evict() takes
    h.contextHash();   // the context it was stored under
});

query() and score() are what make a cache hit auditable - whether the threshold is set where it should be, or whether the cache is answering a question the user did not ask. The advisor publishes the whole hit in the response context under SemanticCacheAdvisor.CACHE_HIT, so a caller can tell a cached answer from a generated one:

ChatClientResponse response = chatClient.prompt().user(question)
        .advisors(semanticCacheAdvisor)
        .call()
        .chatClientResponse();

CacheHit hit = (CacheHit) response.context().get(SemanticCacheAdvisor.CACHE_HIT);
if (hit != null) {
    logger.info("answered from cache, matched '{}' at {}", hit.query(), hit.score());
}

What is stored

Each entry is one document under <prefix><entry id>:

Field Holds
content the question, which is what gets embedded and matched
embedding its vector
context_hash the context, indexed as TAG - the only indexed metadata
response the cached response, stored in the document but deliberately not indexed

The response is read back by key once a match is found, rather than being indexed, so a cached response is not tokenized into the search index.

A response is stored as its generations, each carrying the generated text and the metadata of the generated message. Provider-specific response metadata such as token usage and finish reason is not preserved, so a cached response is not a byte-for-byte copy of the original.

Storing replaces rather than accumulates. Storing a response for a question already answered by a similar enough entry in the same context replaces that entry, so the cache does not fill with near-duplicates of the same question.