DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library
  1. DZone
  2. Refcards
  3. Java Caching Essentials
refcard cover
Refcard #216

Java Caching Essentials

Unlocking Lower Latencies, Reducing Costs, and Enabling Agentic Architectures

Caching is a powerful tool for reducing latency, cutting costs, and enabling scalable system design. This Refcard explores core caching concepts and demonstrates how to implement them using Java’s JCache API. You'll learn how to configure caches, handle expiration and events, choose the right deployment model, and more (with practical examples), helping prepare you to build efficient and flexible Java applications.

Free PDF for Easy Reference
refcard cover

Written By

author avatar Granville Barnett
Architect, Hazelcast
Table of Contents
► Introduction ► Caching Overview ► JCache Essentials ► Conclusion
Section 1

Introduction

The role of caching in systems is increasingly important for unlocking a large category of use cases at scale. Caches have enabled low cost and scalable access to information such as session state and data stores for decades. More modern use cases of caching are enabling low costs and scalable tooling and embedding generation in agentic architectures, which are unlocking the next generation of system innovation.

This Refcard presents ways in which caching can be incorporated into a system using Java's JCache (Java Temporary Caching API). The fundamentals of caching are discussed, followed by a brief tour of the JCache API with code examples, concluding with a summary of cache deployment architectures.

Section 2

Caching Overview

A cache is a store of results previously computed so that subsequent computations may be omitted. The simplest way to think of a cache is as a key-value store: For a given input (key) the output (value) represents a result from a previous computation over that input. A cache hit denotes the presence of specific data in the cache, in which case its value can be used. Otherwise, there is a cache miss, and the relevant computation is undertaken and its output is placed in the cache. The cost of a cache miss may entail network communication in addition to expensive compute operations.

Figure 1: A simplified cache hit/miss flow

Caching is employed to reduce latency and drive down operational costs and has been critical to enabling a large category of applications for decades. Examples of cached data include session state for web applications, database query results, and web page renders, as well as results from generic network and compute expensive operations. A more modern usage of caching is its role in AI. Here, the use of caching reduces expensive API calls (e.g., embedding generation) and minimizes conversational stuttering (e.g., due to tool invocations and network communication) between agents in agentic architectures, unlocking a new wave of solutions and user experiences.

Caches can reside in-process, within a service as part of a client-server architecture, or a combination of the two. Furthermore, deployments of caches can often be composed. For example, applications may communicate with a cache service co-located in the same data center, with the data center local caches being caches of a cache spanning multiple data centers. This flexibility, combined with the category of application that caches enable, has made caching a dominant abstraction for decades.

The remainder of this Refcard discusses JCache — Java's abstraction for incorporating caching into your application — starting with a brief overview of the classes you will frequently use and then progressing into features afforded by the broader features of JCache. We conclude with a summary of cache deployment strategies.

Section 3

JCache Essentials

JCache was introduced in Java Specification Request (JSR) 107 and provides a set of abstractions over caching. There are two standout properties of JCache:

  1. JCache is a specification. JSRs are specifications designed and submitted by an expert group and eventually approved by the Java Community Process Executive Committee. Because JCache is a specification, it insulates itself from implementations whose APIs change frequently.
  2. JCache is provider independent. A side effect of JCache being a specification is that providers of caching solutions can integrate with JCache by implementing its exposed Service Provider Interface (SPI). This provides system designers with flexibility and avoids vendor lock-in.

The following is a simple example of JCache to give an understanding of what its use looks like. The coordinate for the javax.cache dependency can be found here.

Java
 
import java.util.Map;
import javax.cache.Cache;
import javax.cache.CacheManager;
import javax.cache.Caching;
import javax.cache.configuration.MutableConfiguration;
import javax.cache.spi.CachingProvider;

public class App {
    public static void main(String[] args) {
        CachingProvider cachingProvider =                                                            Caching.getCachingProvider(); // (1)
        CacheManager cacheManager = cachingProvider.getCacheManager(); // (2)
        MutableConfiguration<String, String> cacheConfig = new MutableConfiguration<String, String>(); // (3)
        Cache<String, String> cache = cacheManager.createCache("dzone-cache", cacheConfig); // (4)
        cache.put("England", "London"); // (5)
        cache.putAll(Map.of("France", "Paris", "Ireland", "Dublin")); // (6)
        assert cache.get("England").equals("London"); // (7)
        assert cache.get("Italy") == null; // (8)
    }
}


A brief explanation of the above example:

(1) Obtains a handle to the underlying caching provider
(2) Manages a cache's lifecycle (e.g., creating and destroying caches)
(3) Permits enabling/disabling specific functionality of the cache (e.g., statistics, entry listeners)
(4) Creates the cache backed by the cache provider
(5) Puts a single key-value entry in the cache
(6) Puts key-value entries into the cache
(7) Asserts the presence of a cache entry
(8) Asserts that an entry is not present in the cache

The remainder of this section discusses, in more detail, the abstractions introduced in the above example in addition to other methods of the relevant classes that you will often encounter.

The javax.cache.spi.CachingProvider forms the JCache SPI that caching providers can integrate. The most common feature you will use is attaining a reference to a CacheManager. We will discuss Caching later. getCacheManager is the simplest of the getCacheManager variants. This will attain a CacheManager according to the provider defaults.Caches can be created and destroyed using javax.cache.CacheManager:

  • createCache creates a cache of a given name and configuration.
  • destroyCache destroys the cache with the given name.

javax.cache.Cache is an abstraction over the provider's cache and exposes a handful of operations to query and mutate the cache's items:

  • put and putAll put entries into the cache. Note that these methods do not return any previously associated values with the keys being put.
  • containsKey tests if a key exists in the cache.
  • get and getAll return the values associated with the keys specified.
  • remove and removeAll remove items from the cache. 

JCache Packages

In this section, we give a quick overview of some important interfaces within the broader package structure of javax.cache and provide examples of functionality commonly used. We can refer to the documentation to explore an exhaustive list of their content.

Figure 2: Constituent packages of javax.cache

javax.cache

Facilities for general management (CacheManager) and interaction with caches (Cache) reside within the javax.cache package. Beyond initial configuration, unless you want to add additional capabilities to your cache, you can do a lot using just the types within this package. For example, the sample usage from the introduction of the "JCache Essentials" section largely uses the interfaces defined in javax.cache.

javax.cache.configuration

During the creation of a cache, you may want to add capabilities such as enabling statistics or read-through caching. This package provides a Configuration interface and an implementation MutableConfiguration that can be used for such purposes.

Java
 
// ...
MutableConfiguration<String, String> cacheConfig = new MutableConfiguration<String, String>();
cacheConfig.setManagementEnabled(true).setStatisticsEnabled(true);
Cache<String, String> cache = cacheManager.createCache("dzone-cache", cacheConfig);


javax.cache.expiry

There are times when you want items residing within your cache to expire. For example, we might have a cache of home insurance quotes that are valid for 24 hours. In this case, we can use an expiry policy as follows:

Java
 
// ...
MutableConfiguration<String, Double> cacheConfig = new MutableConfiguration<String, Double>();
cacheConfig.setExpiryPolicyFactory(CreatedExpiryPolicy.factoryOf(Duration.ONE_DAY));
Cache<String, Double> cache = cacheManager.createCache("insurance-home-qoutes", cacheConfig);
cache.put(quote.getId(), qoute.getValue());


The javax.cache.expiry package provides additional expiration policies that can be useful for other scenarios. For example, AccessedExpiryPolicy permits the ability to attach an expiration based on a cache entry's last access time.

javax.cache.event

A powerful feature of JCache is the ability to subscribe to cache events. For example, we may want to run some domain logic upon a cache entry being created or deleted. The javax.cache.event package provides the abstractions to do this, specifically the ability to subscribe to cache creates, updates, expiries, and removals. The following basic example runs some domain logic after a cache entry is created:

Java
 
// ...
CacheEntryCreatedListener<String, String> createdListener = new CacheEntryCreatedListener<String, String>() {
  @Override
  public void onCreated(Iterable<CacheEntryEvent<? extends String, ? extends String>> events)
    throws CacheEntryListenerException {
    for (var c : events) {
      performDomainLogic(c);
    }
  }
};
MutableConfiguration<String, String> cacheConfig = new MutableConfiguration<String, String>();
MutableCacheEntryListenerConfiguration<String, String> listenerConfig = new  MutableCacheEntryListenerConfiguration<>(() -> createdListener, null, false, true); // refer to documentation
cacheConfig.addCacheEntryListenerConfiguration(listenerConfig);
Cache<String, String> cache = cacheManager.createCache("events", cacheConfig);
cache.put("key", "value"); // calls creation listener


javax.cache.processor 

A powerful component of JCache is the ability to use an EntryProcessor to move compute to the data and then invoke that compute programmatically. This is particularly powerful when using a provider that hosts its cache within a distributed system (e.g., Hazelcast) as it offers a simple entry point into distributed computing with little investment. The following is a simple example of EntryProcessor that appends a UUID to a cache entry:

Java
 
// ...
class AppendUuidEntryProcessor implements EntryProcessor<String, String, String> {
  @Override
  public String process(MutableEntry<String, String> entry, Object... arguments) throws EntryProcessorException {
    if (entry.exists()) {
      String newValue = entry.getValue() + "-" + UUID.randomUUID();
      entry.setValue(newValue);
      return newValue;
    }
    return null;
  }
}
// ...
cache.invoke(key, new AppendUuidEntryProcessor())


javax.cache.management

The management hooks provided by JCache are very powerful and easily enabled. For example, the below small code snippet exposes Managed Beans defined by the Java Management Extensions (JMX) specification. This enables JMX clients such as jconsole and JDK Mission Control to have visibility into cache configurations and statistics (e.g., hit and miss percentages, average get and put times).

Java
 
// ...
MutableConfiguration<String, String> cacheConfig = new MutableConfiguration<String, String>();
cacheConfig.setManagementEnabled(true).setStatisticsEnabled(true);
Cache<String, String> cache = cacheManager.createCache("management", cacheConfig);
// ...


javax.cache.spi

The example provided in the"JCache Essentials" section omitted how we register the cache provider, the service that hosts the caches our application interacts with using the JCache API. This is where the SPI component of JCache comes into play. There are two components for making this work:

  1. Adding our caching provider to the classpath
  2. Telling JCache to use that provider

Step one is straightforward: Simply add a dependency on any JSR 107-compliant provider.

Step two has a few general approaches you can take:

  • We can tell JCache via invoking one of the Caching#getCachingProvider(...) variants (among others).
  • We can ship a META-INF/services/javax.cache.spi.CachingProvider and have it specify the provider implementation. The provider specified is the fully qualified name of your provider's cache provider implementation.
  • We can use Caching#getCachingProvider(); however, it is best to explicitly qualify the provider to use as you may have multiple providers on your classpath, which will throw a javax.cache.CacheException.

For example, the following specifies Hazelcast as the provider using CachingProvider Caching.getCachingProvider(String):

Java
 
CachingProvider cachingProvider = Caching.getCachingProvider("com.hazelcast.cache.HazelcastCachingProvider");
CacheManager cacheManager = cachingProvider.getCacheManager();
MutableConfiguration<String, String> cacheConfig = new MutableConfiguration<String, String>();
Cache<String, String> cache = cacheManager.createCache("spi-example", cacheConfig);
cache.put("k", "v");


javax.cache.annotation 

JCache defines a number of annotations for integration into context and dependency injection environments. The Spring Framework has native support for JCache annotations. We can refer to the JCache documentation for more information.

javax.cache.integration

The javax.cache.integration package provides CacheLoader (requires read-through) and CacheWriter (requires write-through). The CacheLoader is used when reading data into the cache — for example, Cache#loadAll(...). The CacheWriter can be used as an integration point to propagating cache mutations (e.g., writes, deletes) to an external storage service.

Cache Deployments

JCache has no notion of cache deployment strategies; it is simply an API over caching providers. However, different providers facilitate different types of cache deployments. Consider what caching deployment makes sense for your application and work backward from there to determine an appropriate caching provider.

Figure 2: Caching deployment examples

Note that some caching providers may support all three of these cache deployments, while other providers may not.

The rest of this section discusses the common caching deployments shown in Figure 2:

  • Embedded – The cache is co-located in the same process as the application.
  • Client-server – The cache is hosted in a separate service, and the client communicates with that service to determine cache residency.
  • Embedded/client-server – This is a hybrid mode where the entire cache is hosted on a distinct service, but the client has a smaller cache co-located in the same process.

It is important to note that the above cache deployments are not mutually exclusive; they can be composed in a multitude of ways to meet application demands.

The simplest deployment of a cache is for the cache to reside in the same process as the application, which has the benefit of providing low-latency cache access. Embedded caches are not shared across applications and can have a significant cost to host (the resources they require) and rebuild upon application restart or failure.

A client-server cache deployment has the cache hosted by a service distinct to that of the client. A cache service permits catering for fault tolerance through replication across the service, greater capacity, more scalability options, and the ability to share the cache across applications. The main drawback of the client-server model is the cost of the network communication during a client cache query.

Combined embedded/client-server deployments are where we have an embedded cache, which entails a subset of cache entries from the service cache, populated as a side effect of application cache requests. Here, the client can have low-latency cache hits on frequently accessed data (or data that exhibits a particular access pattern) and forgo the network communication, entailed by communicating with the cache service. Some providers will take care of updating embedded caches with the service hosted cache should they be out of date.

Section 4

Conclusion

This Refcard has presented caching and how it can be used with Java's JCache API. The JCache API is intuitive, powerful, and avoids vendor lock-in due to it being a specification, providing architects and system designers the flexibility they require. This flexibility is particularly important as we enter a new generation of innovation based on agentic architectures, where caching is fundamental for tooling and embedding generation.

Additional resources:

  • Java Application Containerization and Deployment by Mark Heckler, DZone Refcard
  • Java Community Process(SM)

Like This Refcard? Read More From DZone

related article thumbnail

DZone Article

A Way Around Cache Eviction
related article thumbnail

DZone Article

Why 1000 == 1000 Returns False, but 100 == 100 Returns True in Java?
related article thumbnail

DZone Article

Cache in Java With LRU Eviction Policy
related article thumbnail

DZone Article

Stop Loading Everything into Redshift: A Spectrum + Iceberg Pattern for Hybrid Analytics
related refcard thumbnail

Free DZone Refcard

Java Application Containerization and Deployment
related refcard thumbnail

Free DZone Refcard

Introduction to Cloud-Native Java
related refcard thumbnail

Free DZone Refcard

Java 15
related refcard thumbnail

Free DZone Refcard

Java 14
  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook