Clustered Counters with Embedded Infinispan

What You Will Learn

How to configure and use Infinispan clustered counters in embedded mode, including strong bounded counters, strong unbounded counters, and weak counters.

Prerequisites

  • Java 17+

Step 1: Add the Counter Dependency

Add the Infinispan clustered counter module to your pom.xml:

<dependency>
   <groupId>org.infinispan</groupId>
   <artifactId>infinispan-clustered-counter</artifactId>
</dependency>

Step 2: Define Counters in the Global Configuration

Use CounterManagerConfigurationBuilder to define counters when building the global configuration:

      // Setup up a clustered cache manager
      GlobalConfigurationBuilder global = GlobalConfigurationBuilder.defaultClusteredBuilder();
      // Create the counter configuration builder
      CounterManagerConfigurationBuilder builder = global.addModule(CounterManagerConfigurationBuilder.class);
      // Create 3 counters.
      // The first counter is bounded to 10 (upper-bound).
      builder.addStrongCounter().name("counter-1").upperBound(10).initialValue(1);
      // The second counter is unbounded
      builder.addStrongCounter().name("counter-2").initialValue(2);
      // And finally, the third counter is a weak counter.
      builder.addWeakCounter().name("counter-3").initialValue(3);

      // Initialize the cache manager
      cm1 = new DefaultCacheManager(global.build());

      // Retrieve the CounterManager from the CacheManager. Each CacheManager has it own CounterManager
      counterManager = EmbeddedCounterManagerFactory.asCounterManager(cm1);

Step 3: Use Strong Counters

Strong counters provide consistent values during increment and decrement operations. All methods return CompletableFuture:

      // StrongCounter provides the higher consistency. Its value is known during the increment/decrement and it may be bounded.
      // Bounded counters are aimed for uses cases where a limit is needed.
      counter1 = counterManager.getStrongCounter("counter-1");
      // All methods return a CompletableFuture. So you can do other work while the counter value is being computed.
      counter1.getValue().thenAccept(value -> System.out.println("Counter-1 initial value is " + value)).get();

      // Try to add more than the upper-bound
      counter1.addAndGet(10).handle((value, throwable) -> {
         // Value is null since the counter is bounded and we can add 10 to it.
         System.out.println("Counter-1 Exception is " + throwable.getMessage());
         return 0;
      }).get();

      // Check the counter value. It should be the upper-bound (10)
      counter1.getValue().thenAccept(value -> System.out.println("Counter-1 value is " + value)).get();

      //Decrement the value. Should be 9.
      counter1.decrementAndGet().handle((value, throwable) -> {
         // No exception this time.
         System.out.println("Counter-1 new value is " + value);
         return value;
      }).get();

      // Similar to counter-1, counter-2 is a strong counter but it is unbounded. It will never throw the CounterOutOfBoundsException
      counter2 =  counterManager.getStrongCounter("counter-2");

      // All counters allow a listener to be registered.
      // The handle can be used to remove the listener
      counter2.addListener(event -> System.out
              .println("Counter-2 event: oldValue=" + event.getOldValue() + " newValue=" + event.getNewValue()));

      // Adding MAX_VALUE won't throws an exception. But the all the increments won't have any effect since we can store
      //any value larger the MAX_VALUE
      counter2.addAndGet(Long.MAX_VALUE).thenAccept(aLong -> System.out.println("Counter-2 value is " + aLong)).get();

      // Conditional operations are allowed in strong counters
      counter2.compareAndSet(Long.MAX_VALUE, 0)
              .thenAccept(aBoolean -> System.out.println("Counter-2 CAS result is " + aBoolean)).get();
      counter2.getValue().thenAccept(value -> System.out.println("Counter-2 value is " + value)).get();

      // Reset the counter to its initial value (2)
      counter2.reset().get();
      counter2.getValue().thenAccept(value -> System.out.println("Counter-2 initial value is " + value)).get();

Step 4: Use Weak Counters

Weak counters are optimized for speed. Their value is computed lazily:

      // Retrieve counter-3
      counter3 = counterManager.getWeakCounter("counter-3");
      // Weak counter doesn't have its value available during updates. This makes the increment faster than the StrongCounter
      // Its value is computed lazily and stored locally.
      // Its main use case is for uses-case where faster increments are needed.
      counter3.add(5).thenAccept(aVoid -> System.out.println("Adding 5 to counter-3 completed!")).get();

      // Check the counter value.
      System.out.println("Counter-3 value is " + counter3.getValue());

Step 5: Run the Tutorial

mvn package exec:java

The output shows counter operations including bounded overflow handling, compare-and-set, reset, and weak counter updates.

What’s Next