Spring Boot Reactive Remote Caching

What You Will Learn

How to use Spring Boot’s @EnableCaching with Project Reactor (Mono) and the Infinispan Spring Boot remote starter for reactive caching against an Infinispan Server.

Prerequisites

  • Java 17+

  • Spring Boot

  • An Infinispan Server running on localhost:11222 (or Docker/Podman available for Testcontainers)

Step 1: Add Dependencies

Add the Infinispan Spring Boot remote starter with Spring WebFlux:

<dependency>
   <groupId>org.infinispan</groupId>
   <artifactId>infinispan-spring-boot4-starter-remote</artifactId>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
   <groupId>org.infinispan.protostream</groupId>
   <artifactId>protostream-processor</artifactId>
</dependency>

Step 2: Enable Reactive Caching

In application.properties, enable the reactive mode:

infinispan.remote.server-list=127.0.0.1:11222
infinispan.remote.reactive=true

Step 3: Return Mono from Cached Methods

Annotate methods that return Mono with @Cacheable. The Infinispan reactive cache adapter handles the subscription:

@Component
@CacheConfig(cacheNames = Data.BASQUE_NAMES_CACHE)
public class BasqueNamesRepository {

   private static final Logger logger = Logger.getLogger(MethodHandles.lookup().lookupClass().getName());
   private static Map<String, BasqueName> database  = createDB();

   @Cacheable
   public Mono<BasqueName> findById(String id) {
      logger.info("Call database to FIND name by id '" + id + "'");
      return Mono.fromCallable(() -> database.get(id));
   }

Step 4: Configure the Remote Cache and Schema

Register the Protobuf schema and cache configuration:

@Configuration
public class InfinispanConfiguration {

   @Bean
   @Order(Ordered.HIGHEST_PRECEDENCE)
   public InfinispanRemoteCacheCustomizer configurer() {
      return b -> {
         String host = TutorialsConnectorHelper.HOST;
         int port = TutorialsConnectorHelper.SINGLE_PORT;
         if (TutorialsConnectorHelper.INFINISPAN_CONTAINER != null) {
            port = TutorialsConnectorHelper.INFINISPAN_CONTAINER.getFirstMappedPort();
         }
         b.addServer().host(host).port(port);
         b.security().authentication()
                 .username(TutorialsConnectorHelper.USER)
                 .password(TutorialsConnectorHelper.PASSWORD);
         System.out.println(String.format("Connect to the Infinispan Console " +
                         "by login to http://localhost:%d with %s and %s", port,
                 TutorialsConnectorHelper.USER,
                 TutorialsConnectorHelper.PASSWORD));


         URI cacheConfigUri;
         try {
            cacheConfigUri = this.getClass().getClassLoader().getResource("basquesNamesCache.xml").toURI();
         } catch (URISyntaxException e) {
            throw new RuntimeException(e);
         }

         b.remoteCache(Data.BASQUE_NAMES_CACHE)
                 .configurationURI(cacheConfigUri);

         b.remoteCache(Data.BASQUE_NAMES_CACHE).marshaller(ProtoStreamMarshaller.class);

         // Add marshaller in the client, the class is generated from the interface in compile time
         b.addContextInitializer(new BasquesNamesSchemaBuilderImpl());
      };
   }
}

Step 5: Run the Tutorial

mvn spring-boot:run

The application periodically looks up Basque names. Watch the logs: the first lookup for each id logs "Call database", while subsequent lookups return from the Infinispan cache without the log message.

What’s Next