
Distributed Cache Layering with Infinispan and Quarkus
There are several ways to go by application data caching within Quarkus and the OOTB solution is usually relying on a local application cache. How could you go by abstracting that layer and have a distributed cache that you can interact as if it were local configured cache? We will be using Infinispan as a distributed cache, CDI interceptor beans and Infinispan client to show one way.
Prerequisites
- Java 8+
- Lombok ≥ 1.18.18
- Maven ≥ 3.6.2
- Docker
Preface
Currently this application cache fraction is considered preview in the eyes of the community.
This technology is considered preview.
In preview, backward compatibility and presence in the ecosystem is not guaranteed. Specific improvements might require to change configuration or APIs and plans to become stable are under way. Feedback is welcome on our mailing list or as issues in our GitHub issue tracker.
For a full list of possible extension statuses, check our FAQ entry.
Quarkus Application Cache Setup
Normally, this is the procedure one would go by when setting up the local cache.
From the guide QUARKUS — APPLICATION DATA CACHING
Adding the proper dependency.
./mvnw quarkus:add-extension -Dextensions="cache"
Or
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-cache</artifactId>
</dependency>
API Resource
Defining an API resource with the Quarkus Cache annotation.
@Path("/v1/service")
public class Service {
@CacheResult(cacheName = "some-cache")
public String getValue(String key) {
// return some cache value
}
}
@CacheResult
This loads a method result from the cache without executing the method body whenever possible.
When a method annotated with
@CacheResult
is invoked, Quarkus will compute a cache key and use it to check in the cache whether the method has been already invoked. If the method has one or more arguments, the key computation is done from all the method arguments if none of them is annotated with@CacheKey
, or all the arguments annotated with@CacheKey
otherwise. Each non-primitive method argument that is part of the key must implementequals()
andhashCode()
correctly for the cache to work as expected.
This annotation can also be used on a method with no arguments, a default key derived from the cache name is used in that case. If a value is found in the cache, it is returned and the annotated method is never actually executed. If no value is found, the annotated method is invoked and the returned value is stored in the cache using the computed key.
The underlying cache provider at the moment is however caffeine and its locally for the application itself. Perhaps we could leverage the forementioned topics above and provide the same functionality for a distributed cache like Infinispan. Let’s give it a shot.
Running a distributed cache
For our purpose in this case we will be using Infinispan cache and just the relevant extensions. There are two modes running Infinispan, infinispan embedded or infinispan remote. The embedded focuses on the infinispan cache located or embedded in your Java Application. The remote is the mode which integrates towards a separated Infinispan Server. We will be focusing on the latter.
What is Infinispan?
Infinispan is an open-source in-memory data grid that offers flexible deployment options and robust capabilities for storing, managing, and processing data. Infinispan provides a key/value data store that can hold all types of data, from Java objects to plain text. Infinispan distributes your data across elastically scalable clusters to guarantee high availability and fault tolerance, whether you use Infinispan as a volatile cache or a persistent data store.
Infinispan offers a so called HotRod client which is a binary client that sits on top of the TCP which offers high scalability, useful querying and other rich features.
./mvnw quarkus:add-extension -Dextensions="quarkus-infinispan-client"
Or
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-infinispan-client</artifactId>
</dependency>

Here is how we run Infinispan server.
docker run -p 11222:11222 -e USER="Titus Bramble" -e PASS="Shambles" quay.io/infinispan/server:12.1.4.Final
Interceptors
An interceptor will come in handy when intercepting requests and providing cached responses. They come with the CDI specification and deal with different type of functionality such as transactions, security, logging aspects, etc. They normally don’t adhere to the logic of the code and should be treated independently.
Just as the@CacheResult
is the interceptor binding for quarkus-cache. Let’s add a custom interceptor binding to start using our custom interceptor. It should look something like this.
@InterceptorBinding
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, METHOD})
public @interface Cached {
@Nonbinding String cacheName();
}
Now we can start adding our logic for our custom cache interceptor that will have logic for cache creation, asynchronous retrieval and writes if the key is none existent. Since we will be abstracting Infinispan, it will be a simple replicated cache with a string key and string value store. Our interceptor will be using reactive mutiny streams as a result in the invocation.
API Resource
Just a simple value response that returns a response from an otherwise time consuming process.
@Path("/v1/services")
public class CacheResource {
@GET
@Produces(MediaType.TEXT_PLAIN)
@Path("{id}")
@Cached(cacheName = "my-cache")
public Uni<String> getServiceValue(@PathParam("id") Long path) {
// time consuming process return Uni.createFrom().item("CacheItemValue");
}
}
Configuration
The Quarkus configuration we are using. More cache configuration and options can be found here.
https://infinispan.org/docs/stable/index.html
quarkus:
infinispan-client:
auth-username: "Titus Bramble"
auth-password: "Shambles"
auth-realm: default
auth-server-name: infinispan
sasl-mechanism: DIGEST-MD5
cache:
<infinispan>
<cache-container>
<replicated-cache name="<name-override>">
<encoding>
<key media-type="application/x-java-object"/>
<value media-type="application/x-java-object"/>
</encoding>
</replicated-cache>
</cache-container>
</infinispan>
Response
Performing the first request to our resource, should yield an actual response and it should live with a TTL flag set to 60 seconds. Next subsequent request will give the cached response for duration of that time.
$ curl "http://localhost:8080/v1/services/1"
We can see directly in the Infinispan console our newly created cache record and its properties
http://localhost:11222/console/

From here and onward, we can start expanding and scope our different caches according to our needs or business requirements. This just demonstrates, even though application data caching within Quarkus is local out of the box, similar patterns can be achieved using proper interceptors and annotated methods.
If you want to see a more full fledged example, you can find the source on GitHub.
Find the GitHub source project here
Good luck!
Inspired by: