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)

Start an Infinispan Server with Docker or Podman:

docker run -it --rm -p 11222:11222 -e USER=admin -e PASS=password quay.io/infinispan/server:latest
Tip
You can replace docker with podman in the command above if you use Podman.
Tip
If no server is running, the tutorial code automatically starts an Infinispan Server using 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