Getting Started with Infinispan data grid -the right way

Infinispan is a distributed in-memory key/value data grid. An in-memory data grid is a form of middleware that stores sets of data for use in one or more applications, primarily in memory. The data is typically distributed across grid nodes in a network.

Updated with the latest version of Infinispan: 15.0.0.Dev10 – March 2024

You can use Infinispan as an embedded Java library and as a language-independent Remote Service that can be accessed over a variety of protocols (Hot Rod, REST, Memcached and WebSockets). It offers advanced functionality too. For example: transactions, events, querying and distributed processing as well as numerous integrations with frameworks such as the JCache API standard, CDI, Hibernate, WildFly, Spring Cache, Spring Session, Lucene, Spark and Hadoop.

How does Infinispan work

Infinispan main API is the org.infinispan.Cache which extends java.util.concurrent.ConcurrentMap and closely resembles javax.cache.Cache from JSR 107. This is the most performant API to use, and you should use it for all new projects.

A Cache provides a highly concurrent, optionally distributed data structure with additional features such as:

  • Eviction and expiration support to prevent OutOfMemoryErrors
  • JTA transaction compatibility
  • Event notification via the Listeners
  • Persistence of entries to a CacheStore, to maintain copies that would withstand server failure or restarts

There are several use cases for Infinispan:

  • Local cache: In this case, you are using Infinispan as a fast in-memory cache of frequently accessed data. Using Infinispan is better than using a simple ConcurrentHashMap, since it has additional useful features such as expiration and eviction.
  • Clustered cache: If your cache doesn’t fit in a single node, or you want to invalidate entries across multiple instances of your application, Infinispan can scale horizontally to several hundred nodes.
  • Clustering building block for your applications: for example WildFly uses Infinispan to make your applications cluster-aware and get access to features like topology change notifications, cluster communication and clustered execution.
  • Remote cache: If you want to keep your caching layer separate from your application or you need to make your data available to different applications, possibly even using different languages / platforms, you can use Infinispan Server and its various clients.
  • Data grid: you can use Infinispan as your primary store and use its powerful features such as transactions, notifications, queries, distributed execution, etc.

How to configure infinispan cache

Cache instances can be retrieved using an appropriate CacheManager, which represents a collection of caches. The CacheManager has many purposes:

  • Acts as a container for caches and controls their lifecycle
  • Manages global configuration and common data structures and resources (e.g. thread pools)
  • Manages clustering
EmbeddedCacheManager cacheManager = new DefaultCacheManager();

This will give us a default local (i.e. non-clustered) CacheManager. Because a CacheManager holds on to some resources which require proper disposal, you also need to make sure you stop it when you don’t need it anymore. This is done with the stop() method, as follows:

cacheManager.stop();

Once you stop a CacheManager, and all resources obtained from it, you cannot use it anymore.

There are two approaches you can use to configure Infinispan, Either declaratively using an XML configuration file, or programmatically via the fluent configuration API.

The XML approach is the most common approach, as it relies on an XML file, which is parsed when Infinispan starts. It is easy if you have your entire configuration in one place.

Let’s see with a concrete example how to configure a Cache programmatically and using an XML Configuration.

Infinispan Hello World

In this basic example, we will use a Local Cache bound to a single JVM process. We will store cluster data in a local node only. Here is a view of our simple Local Cache:

"

Firstly, we will create a simple Java project using the simplest Maven archetype:

$ mvn archetype:generate -DgroupId=com.mastertheboss -DartifactId=infinispan-demo -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4 -DinteractiveMode=false

Next, we will add infinispan-core dependency into the project:

<project xmlns="http://maven.apache.org/POM/4.0.0"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.mastertheboss</groupId>
	<artifactId>infinispan-demo</artifactId>
	<version>1.0-SNAPSHOT</version>

	<name>infinispan-demo</name>
	<url>https://www.mastertheboss.com</url>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<maven.compiler.source>1.8</maven.compiler.source>
		<maven.compiler.target>1.8</maven.compiler.target>
		<infinispan.version>15.0.0.Dev10</infinispan.version>
	</properties>

	<dependencies>

		<dependency>
			<groupId>org.infinispan</groupId>
			<artifactId>infinispan-core</artifactId>
			<version>${infinispan.version}</version>
		</dependency>

		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.11</version>
			<scope>test</scope>
		</dependency>
	</dependencies>

        <!-- build configuration here -->
</project>

Then, let’s change the App.java class to boostrap a local Infinispan cache:

package com.mastertheboss;

import org.infinispan.Cache;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.manager.DefaultCacheManager;

public class App 
{
 	
    public static void main( String[] args )
    {
    	  DefaultCacheManager cacheManager = new DefaultCacheManager();
          // Define local cache configuration
          cacheManager.defineConfiguration("local", new ConfigurationBuilder().build());
          // Obtain the local cache
          Cache<String, String> cache = cacheManager.getCache("local");
          // Store a key
          cache.put("name", "john");
          // Retrieve the value for they key and print it out
          System.out.printf("name = %s\n", cache.get("name"));
          // Stop the cache manager and release all resources
          cacheManager.stop();
      	  
    }
}

You can run the example as follows:

$ mvn clean compile exec:java -Dexec.mainClass=com.mastertheboss.App

The Console will output:

name = john

Using a declarative Infinispan configuration

In the next example, we are defining a named cache “local” using a default configuration, which means a basic local cache. Caches in Infinispan are “named” which means there is an unique name for them.

In this example, we will be using the configuration from an external XML file container in the src/main/resources folder of your Maven project:

package com.mastertheboss;

import org.infinispan.Cache;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.manager.DefaultCacheManager;

public class App2 
{
 	
    public static void main( String[] args ) throws Exception
    {
    	  DefaultCacheManager cacheManager = new DefaultCacheManager("config.xml");
     	  Cache cache = cacheManager.getCache();
          cache.put("key", "value");
          System.out.printf("key = %s\n", cache.get("key"));
          cacheManager.stop();
      	  
    }
}

Here is the external configuration file (config.xml) which contains the simplest possible configuration for each of Infinispan’s cache types:

<infinispan>
   <cache-container default-cache="local">
      <transport cluster="mycluster"/>
      <local-cache name="local"/>
      <invalidation-cache name="invalidation" mode="SYNC"/>
      <replicated-cache name="repl-sync" mode="SYNC"/>
      <distributed-cache name="dist-sync" mode="SYNC"/>
   </cache-container>
</infinispan>

You can find the source code for the above example here: https://github.com/fmarchioni/mastertheboss/tree/master/infinispan/infinispan-demo

Using Listeners to capture events

Infinispan provides a listener API, where clients can register and get notified when cache level events and cache manager level events happen. Events trigger a notification which is dispatched to listeners. Listeners are simple POJO s annotated with @Listener and registered using the methods defined in the Listenable interface. Let’s see how we can register as listener the same Application class:

package com.mastertheboss;

import org.infinispan.Cache;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.manager.DefaultCacheManager;
import org.infinispan.notifications.Listener;
import org.infinispan.notifications.cachelistener.annotation.CacheEntryCreated;
import org.infinispan.notifications.cachelistener.annotation.CacheEntryModified;
import org.infinispan.notifications.cachelistener.event.CacheEntryCreatedEvent;
import org.infinispan.notifications.cachelistener.event.CacheEntryModifiedEvent;
public class AppListener {


	   public static void main(String[] args) {

	      DefaultCacheManager cacheManager = new DefaultCacheManager();
	      cacheManager.defineConfiguration("local", new ConfigurationBuilder().build());
	      Cache<String, String> cache = cacheManager.getCache("local");

	      cache.addListener(new MyListener());
	      // Store some values
	      cache.put("key1", "value1");
	      cache.put("key2", "value2");
	      cache.put("key1", "newValue");
	      // Stop the cache manager and release all resources
	      cacheManager.stop();
	   }
	 
	   @Listener
	   public static class MyListener {
	 
	      @CacheEntryCreated
	      public void entryCreated(CacheEntryCreatedEvent<String, String> event) {
	         if (!event.isPre())
	            System.out.printf("Added entry: %s\n", event.getKey());
	      }
	 
	      @CacheEntryModified
	      public void entryModified(CacheEntryModifiedEvent<String, String> event) {
	         if (event.isPre())
	            System.out.printf("Modified entry: %s\n", event.getKey());
	      }
	   }
	}

Configuring Cache expiration

Data stored in an in-memory data grid is usually the result of an expensive computation or has been retrieved from a data-source. If such data can vary with time, it makes sense to set an expiration time, thus making the cache entries mortal. In Infinispan entry expiration can happen in two ways:

  • After some time after the data was inserted into the cache (i.e. lifespan)
  • After some time since it was last accessed (i.e. maximum idle time)

The Cache interface offers overloaded versions of the put() method that allow specifying either or both expiration properties. The following example shows how to insert an entry which will expire after 1 minute:

cache.put(location, weather, 1, TimeUnit.MINUTES);

You can also set a default expiration lifespan for all entries in a cache via the configuration:

configurationBuilder.expiration().lifespan(1, TimeUnit.MINUTES);

or in XML:

<distributed-cache name="mortaldata">
  <expiration lifespan="60000" /> 
</distributed-cache>

Eviction of Cache Data

Out of the box, caches are unbounded, that is they grow indefinitely and it is up to the application to remove unneeded data. In Infinispan you can also set a maximum size for a cache: when this limit is reached, entries accessed least recently will be evicted. For example, here is how to set a maximum of 1000 entries in the cache:

configurationBuilder.eviction().strategy(EvictionStrategy.LRU).type(EvictionType.COUNT).size(1000);

or in XML:

<distributed-cache name="boundedcache">
  <eviction strategy="LIRS" type="COUNT" size="1000" />
</distributed-cache>

You can also set a maximum memory occupation. For example to set an upper bound of 1MB:

configurationBuilder.eviction().strategy(EvictionStrategy.LIRS).type(EvictionType.MEMORY).size(1000000);

And the equivalent in XML:

<distributed-cache name="boundedcache">
  <eviction strategy="LIRS" type="MEMORY" size="1000000" />
</distributed-cache>

How to clear Infinispan cache

There is no single API to remove all data from the in-memory data grid. If the application uses WildFly’s Infinispan cache (which is however not a recommended option), one could undeploy a particular application, which results in clearing the cache. Apart from that, you can use the following method to clear all entries of a Cache:

cache.entrySet().removeIf(entry -> entry.getKey().indexOf("key-") > 0);

Yet another choice is the cache stream API which may be a little more cumbersome to use, but will provide you the best performance of all of the options:

cache.entrySet().parallelStream()  
   .filter(e -> e.getKey().indexOf("key-") > 0)
   .forEach((cache, e) -> cache.remove(e.getKey()));
Found the article helpful? if so please follow us on Socials