Infinispan implements the JCache API (also known as JSR-107 and javax.cache).

1. Using the JCache (JSR-107) API

Infinispan provides a fully compliant implementation of the JCache (JSR-107) specification, the standard Java API for temporary caching. You can use the JCache API as a portable caching layer that works across different cache providers.

By coding against the javax.cache API, your application remains portable across JCache-compliant implementations.

The JCache specification (JSR-107) is no longer under active development. It was not part of Java EE, has not been adopted by Jakarta EE, and the expert group is no longer active. While Infinispan continues to support the JCache API, no further evolution of the specification is expected.

For new projects, consider using the Infinispan native caching API or other actively maintained alternatives, which offer richer functionality and continued development.

1.1. JCache Dependencies

Add the following dependencies to your pom.xml to use the JCache API with Infinispan:

<dependency>
  <groupId>org.infinispan</groupId>
  <artifactId>infinispan-jcache</artifactId>
</dependency>
<dependency>
  <groupId>javax.cache</groupId>
  <artifactId>cache-api</artifactId>
</dependency>

When these dependencies are on the classpath, Infinispan is automatically discovered as the JCache caching provider through the standard java.util.ServiceLoader mechanism.

1.2. Creating and Using JCache Caches

Create a JCache cache using the standard javax.cache API with Infinispan as the caching provider.

Procedure
  1. Obtain a CachingProvider and CacheManager.

  2. Configure a cache with MutableConfiguration.

  3. Create the cache and perform operations.

import javax.cache.Cache;
import javax.cache.CacheManager;
import javax.cache.Caching;
import javax.cache.configuration.MutableConfiguration;
import javax.cache.spi.CachingProvider;

// Obtain the JCache CachingProvider. Infinispan is automatically
// discovered when infinispan-jcache is on the classpath.
CachingProvider cachingProvider = Caching.getCachingProvider();
CacheManager cacheManager = cachingProvider.getCacheManager();

// Configure a cache with key and value types.
MutableConfiguration<String, String> configuration = new MutableConfiguration<>();
configuration.setTypes(String.class, String.class);

// Create a named cache with the configuration.
Cache<String, String> cache = cacheManager.createCache("myCache", configuration);

After you create a cache, you can store and retrieve values:

// Store a value
cache.put("key", "value");

// Retrieve the value
String value = cache.get("key");
System.out.printf("key = %s%n", value);

When you are finished, close the CacheManager and CachingProvider to release resources:

cacheManager.close();
cachingProvider.close();
Additional resources

1.3. Configuring Read-Through and Write-Through

JCache supports read-through and write-through caching to automatically load and persist cache entries using a CacheLoader and CacheWriter.

1.3.1. Read-through

With read-through enabled, Infinispan invokes the CacheLoader to load entries from an external store on cache misses. Read-through is only triggered by get(), getAll(), and invoke() operations.

import javax.cache.Cache;
import javax.cache.CacheManager;
import javax.cache.Caching;
import javax.cache.configuration.FactoryBuilder;
import javax.cache.configuration.MutableConfiguration;
import javax.cache.spi.CachingProvider;

CachingProvider cachingProvider = Caching.getCachingProvider();
CacheManager cacheManager = cachingProvider.getCacheManager();

MutableConfiguration<String, String> configuration = new MutableConfiguration<>();
configuration.setTypes(String.class, String.class);
configuration.setReadThrough(true);
configuration.setCacheLoaderFactory(
      FactoryBuilder.factoryOf(MyCacheLoader.class));

Cache<String, String> cache = cacheManager.createCache("readThroughCache", configuration);

1.3.2. Write-through

With write-through enabled, Infinispan invokes the CacheWriter to persist entries to an external store on cache mutations.

import javax.cache.Cache;
import javax.cache.CacheManager;
import javax.cache.Caching;
import javax.cache.configuration.FactoryBuilder;
import javax.cache.configuration.MutableConfiguration;
import javax.cache.spi.CachingProvider;

CachingProvider cachingProvider = Caching.getCachingProvider();
CacheManager cacheManager = cachingProvider.getCacheManager();

MutableConfiguration<String, String> configuration = new MutableConfiguration<>();
configuration.setTypes(String.class, String.class);
configuration.setWriteThrough(true);
configuration.setCacheWriterFactory(
      FactoryBuilder.factoryOf(MyCacheWriter.class));

Cache<String, String> cache = cacheManager.createCache("writeThroughCache", configuration);

In the Infinispan JCache implementation, CacheLoader and CacheWriter instances are integrated as Infinispan persistence stores internally. If the underlying Infinispan cache already has a persistence store configured, write operations persist to that store regardless of the isWriteThrough() setting.

1.4. JCache Read-Through and Write-Through Behavior

When using the JCache (JSR-107) API with Infinispan, read-through and write-through interact with the underlying persistence layer through CacheLoader and CacheWriter adapters.

Read-through uses CacheLoader to load entries from an external store on cache misses. Write-through uses CacheWriter to persist entries to an external store on cache mutations.

Read-through and write-through behavior is not fully consistent across all JCache operations. The following table documents the current behavior of each operation for reference.

Table 1. JCache operation store behavior
Operation Read-through (CacheLoader) Write-through (CacheWriter) Notes

get(K)

Conditional on isReadThrough()

Always enabled

CacheLoader called for missing entries

getAll(Set)

Conditional on isReadThrough()

Always enabled

Same as get()

containsKey(K)

Always skip

N/A

Spec: "only the cache is checked"

put(K, V)

Always skip

Always enabled

No isWriteThrough() check

getAndPut(K, V)

Always skip

Always enabled

No isWriteThrough() check

putAll(Map)

Conditional on isWriteThrough()

Always enabled

isWriteThrough() changes load + error handling, not persistence

putIfAbsent(K, V)

Always skip

Always enabled

No isWriteThrough() check

remove(K)

Always skip

Always enabled

No isWriteThrough() check

remove(K, V)

Always skip

Always enabled

No isWriteThrough() check

getAndRemove(K)

Always skip

Always enabled

No isWriteThrough() check

replace(K, V)

Always skip

Always enabled

No isWriteThrough() check

replace(K, V, V)

Always skip

Always enabled

No isWriteThrough() check

getAndReplace(K, V)

Always skip

Always enabled

No isWriteThrough() check

removeAll(Set)

Always skip

Always enabled

isWriteThrough() only changes error handling strategy

removeAll()

Always enabled

Always enabled

Direct cache API; bypasses functional map

invoke(K, EntryProcessor)

Conditional on isReadThrough()

Always enabled

Read-through via getValue(); write-through via setValue()/remove()

invokeAll(Set, EntryProcessor)

Conditional on isReadThrough()

Always enabled

Same as invoke()

clear()

N/A

Always enabled

Spec: "without notifying listeners or CacheWriters"; clears both cache and store

"Always enabled" in the write-through column means that writes always go to the store regardless of the isWriteThrough() setting. This applies both when a JCache CacheWriterFactory is configured and when a persistence store is defined directly in the Infinispan configuration (for example, file-based, JDBC, or RocksDB stores).

Conditional operations such as putIfAbsent, replace, and remove(K, V) only trigger write-through when the mutation actually occurs. For example, replace(K, V) only writes to the store if the entry already exists in memory.

None of these conditional or previous-value-returning operations (getAndPut, getAndRemove, getAndReplace, putIfAbsent, replace, remove(K, V)) trigger read-through. Their existence and value checks only see entries currently in memory. This is consistent with the JSR-107 specification, which restricts read-through to get(), getAll(), and invoke().