Overview of JCache

The Java Temporary Caching API (JSR-107), also referred to as JCache, is a specification (not a software implementation) that defines the javax.cache API. The specification was developed under the Java Community Process, and its purpose is to provide standardized caching concepts and mechanisms for Java applications.

The API is simple to use, it is designed as a caching standard and is vendor-neutral. It eliminates the stark contrast that has in the past existed between APIs of different vendors, which caused developers to stick with the proprietary API they were already using, rather than investigating a new API, as the bar to investigating other products was too high.

So it is easy for you as an application developer to develop an application using the JCache API from one vendor, then if you so choose, try out another vendor’s JCache support without having to change a single line of your application code. All you have to do is use the JCache caching library from your chosen vendor. This means you can avoid having to rewrite a lot of your caching related code in an application just to try out a new caching solution.

Using Ehcache as a JCache Provider

To use JCache API calls in an application, you require both of the following jar files:

  • The JCache jar, which defines the JCache APIs.

  • The Ehcache jar, which is the caching provider jar that implements the JCache APIs. It translates the JCache API calls to their Ehcache API equivalent.

You can use the JCache API to develop a complete application, without the need to use any Ehcache API calls.

Setting up Ehcache as the Caching Provider for JCache

To use Ehcache as the caching provider for your application, add the file javax.cache:cache-api:1.x.y.jar (where x.y is a version-dependent string) to your application’s classpath. This is of course assuming Ehcache is already on that same classpath. No other setup steps are required.

The Ehcache jar file is contained in the product distribution. The JCache jar file is available as a download from the JSR-107 section of the web pages of the Java Community Process.

If you were already using JCache with another caching provider, ensure that you remove the other provider’s jar file before starting your application.

Getting Started with Ehcache and JCache (JSR-107)

In addition to the Cache interface, the JCache specification defines the interfaces CachingProvider and CacheManager. Applications need to use a CacheManager to create/retrieve a Cache. Similarly a CachingProvider is required to get/access a CacheManager.

Here is a code sample that demonstrates the usage of the basic JCache configuration APIs:

CachingProvider provider = Caching.getCachingProvider();  (1)
CacheManager cacheManager = provider.getCacheManager();   (2)
MutableConfiguration<Long, String> configuration =
    new MutableConfiguration<Long, String>()  (3)
        .setTypes(Long.class, String.class)   (4)
        .setStoreByValue(false)   (5)
        .setExpiryPolicyFactory(CreatedExpiryPolicy.factoryOf(Duration.ONE_MINUTE));  (6)
Cache<Long, String> cache = cacheManager.createCache("jCache", configuration); (7)
cache.put(1L, "one"); (8)
String value = cache.get(1L); (9)
1 Retrieves the default CachingProvider implementation from the application’s classpath. This method will work if and only if there is exactly one JCache implementation jar in the classpath. If there are multiple providers in your classpath then use the fully qualified name org.ehcache.jsr107.EhcacheCachingProvider to retrieve the Ehcache caching provider. You can do this by using the Caching.getCachingProvider(String) static method instead.
2 Retrieve the default CacheManager instance using the provider.
3 Create a cache configuration using MutableConfiguration…​
4 with key type and value type as Long and String respectively…​
5 configured to store the cache entries by reference (not by value)…​
6 and with an expiry time of one minute defined for entries from the moment they are created.
7 Using the cache manager, create a cache named jCache with the configuration created in step <3>.
8 Put some data into the cache.
9 Retrieve the data from the same cache.

Integrating JCache and Ehcache Configurations

As mentioned already, JCache offers a minimal set of configuration that is ideal for an in-memory cache. But Ehcache native APIs support topologies that are much more complex and provide more features. At times, application developers might want to configure caches that are much complex (in terms of topology or features) than the ones that the JCache MutableConfiguration permits, and still be able to use JCache’s caching APIs. Ehcache provides several ways to achieve this, as described in the following sections.

Accessing the underlying Ehcache configuration from a JCache configuration

When you create a Cache on a CacheManager using a MutableConfiguration - in other words, using only JCache types - you can still get to the underlying Ehcache CacheRuntimeConfiguration:

MutableConfiguration<Long, String> configuration = new MutableConfiguration<>();
configuration.setTypes(Long.class, String.class);
Cache<Long, String> cache = cacheManager.createCache("someCache", configuration); (1)

CompleteConfiguration<Long, String> completeConfiguration = cache.getConfiguration(CompleteConfiguration.class); (2)

Eh107Configuration<Long, String> eh107Configuration = cache.getConfiguration(Eh107Configuration.class); (3)

CacheRuntimeConfiguration<Long, String> runtimeConfiguration = eh107Configuration.unwrap(CacheRuntimeConfiguration.class); (4)
1 Create a JCache cache using the MutableConfiguration interface from the JCache specification.
2 Get to the JCache CompleteConfiguration.
3 Get to the configuration bridge connecting Ehcache and JCache.
4 Unwrap to the Ehcache CacheRuntimeConfiguration type.

Building the JCache configuration using Ehcache APIs

CacheManager level configuration

If you need to configure features at the CacheManager level, like the persistence directory, you will have to use provider specific APIs.

The way you do this is as follows:

CachingProvider cachingProvider = Caching.getCachingProvider();
EhcacheCachingProvider ehcacheProvider = (EhcacheCachingProvider) cachingProvider; (1)

DefaultConfiguration configuration = new DefaultConfiguration(ehcacheProvider.getDefaultClassLoader(),
  new DefaultPersistenceConfiguration(getPersistenceDirectory())); (2)

CacheManager cacheManager = ehcacheProvider.getCacheManager(ehcacheProvider.getDefaultURI(), configuration); (3)
1 Cast the CachingProvider into the Ehcache specific implementation org.ehcache.jsr107.EhcacheCachingProvider,
2 Create a configuration using the specific Ehcache DefaultConfiguration and pass it some CacheManager level configurations,
3 Create the CacheManager using the method that takes an Ehcache configuration as a parameter.

Cache level configuration

You can also create a JCache Cache using an Ehcache CacheConfiguration. When using this mechanism, no JCache CompleteConfiguration is used and so you cannot get to one.

CacheConfiguration<Long, String> cacheConfiguration = CacheConfigurationBuilder.newCacheConfigurationBuilder(Long.class, String.class,
    ResourcePoolsBuilder.heap(10)).build(); (1)

Cache<Long, String> cache = cacheManager.createCache("myCache",
    Eh107Configuration.fromEhcacheCacheConfiguration(cacheConfiguration)); (2)

Eh107Configuration<Long, String> configuration = cache.getConfiguration(Eh107Configuration.class);
configuration.unwrap(CacheConfiguration.class); (3)

configuration.unwrap(CacheRuntimeConfiguration.class); (4)

try {
  cache.getConfiguration(CompleteConfiguration.class); (5)
  throw new AssertionError("IllegalArgumentException expected");
} catch (IllegalArgumentException iaex) {
  // Expected
}
1 Create an Ehcache CacheConfiguration. You can use a builder as shown here, or alternatively use an XML configuration (as described in the following section).
2 Get a JCache configuration by wrapping the Ehcache configuration.
3 Get back to the Ehcache CacheConfiguration.
4 …​ or even to the runtime configuration.
5 No JCache CompleteConfiguration is available in this context.

Building the JCache configuration using an Ehcache XML configuration

Another way to have the full Ehcache configuration options on your JCache caches while having no code dependency on Ehcache as the cache provider is to use XML-based configuration. See the XML documentation for more details on configuring `Cache`s in XML.

The following is an example of an XML configuration:

<config xmlns='http://www.ehcache.org/v3'>

  <cache alias="ready-cache">
    <key-type>java.lang.Long</key-type>
    <value-type>com.pany.domain.Product</value-type>
    <loader-writer>
      <class>com.pany.ehcache.integration.ProductCacheLoaderWriter</class>
    </loader-writer>
    <heap unit="entries">100</heap>
  </cache>

</config>

Here is an example of how to access the XML configuration using JCache:

CachingProvider cachingProvider = Caching.getCachingProvider();
CacheManager manager = cachingProvider.getCacheManager( (1)
    getClass().getResource("/org/ehcache/docs/ehcache-jsr107-config.xml").toURI(), (2)
    getClass().getClassLoader()); (3)
Cache<Long, Product> readyCache = manager.getCache("ready-cache", Long.class, Product.class); (4)
1 Invoke javax.cache.spi.CachingProvider.getCacheManager(java.net.URI, java.lang.ClassLoader)
2 …​ and pass in a URI that resolves to an Ehcache XML configuration file.
3 The second argument is the ClassLoader to use to load user types if needed; i.e. Class instances that are stored in the Cache managed by our CacheManager.
4 Get the configured Cache out of the CacheManager.

You can alternatively use the CachingProvider.getCacheManager() method that takes no arguments. The URI and ClassLoader used to configure the CacheManager will then use the vendor specific values returned by CachingProvider.getDefaultURI and .getDefaultClassLoader respectively. Be aware that these are not entirely specified for Ehcache currently and may change in future releases!

Enabling/Disabling MBeans for JCache using an Ehcache XML configuration

When using an Ehcache XML configuration, you may want to enable management and / or statistics MBeans for JCache caches. This gives you control over the following:

  • javax.cache.configuration.CompleteConfiguration.isStatisticsEnabled

  • javax.cache.configuration.CompleteConfiguration.isManagementEnabled

You can do this at two different levels:

<config
    xmlns='http://www.ehcache.org/v3'
    xmlns:jsr107='http://www.ehcache.org/v3/jsr107'>


  <service>
    <jsr107:defaults enable-management="true" enable-statistics="true"/> (1)
  </service>

  <cache alias="stringCache"> (2)
    <key-type>java.lang.String</key-type>
    <value-type>java.lang.String</value-type>
    <heap unit="entries">2000</heap>
  </cache>

  <cache alias="overrideCache">
    <key-type>java.lang.String</key-type>
    <value-type>java.lang.String</value-type>
    <heap unit="entries">2000</heap>
    <jsr107:mbeans enable-management="false" enable-statistics="false"/> (3)
  </cache>

  <cache alias="overrideOneCache">
    <key-type>java.lang.String</key-type>
    <value-type>java.lang.String</value-type>
    <heap unit="entries">2000</heap>
    <jsr107:mbeans enable-statistics="false"/> (4)
  </cache>
</config>
1 Using the JCache service extension, you can enable MBeans by default.
2 The cache stringCache will have both MBeans enabled, according to the service configuration.
3 The cache overrideCache will have both MBeans disabled, overriding the service configuration.
4 The cache overrideOneCache will have the statistics MBean disabled, whereas the management MBean will be enabled according to the service configuration.

Supplementing JCache cache configurations using Ehcache XML extensions

You can also create cache-templates. See the Cache Templates section of the XML Documentation for more details. Ehcache as a caching provider for JCache comes with an extension to the regular XML configuration so you can:

  • Configure a default template from which all programmatically created Cache instances inherit, and

  • Configure a given named Cache to inherit from a specific template.

This feature is particularly useful to configure Cache beyond the scope of the JCache specification, for example, giving Cache a capacity constraint. To do this, add a jsr107 service in your XML configuration file:

<config
    xmlns='http://www.ehcache.org/v3'
    xmlns:jsr107='http://www.ehcache.org/v3/jsr107'> (1)

  <service> (2)
    <jsr107:defaults default-template="tinyCache"> (3)
      <jsr107:cache name="foos" template="clientCache"/> (4)
      <jsr107:cache name="byRefCache" template="byRefTemplate"/>
      <jsr107:cache name="byValCache" template="byValueTemplate"/>
      <jsr107:cache name="weirdCache1" template="mixedTemplate1"/>
      <jsr107:cache name="weirdCache2" template="mixedTemplate2"/>
    </jsr107:defaults>
  </service>

  <cache-template name="clientCache">
    <key-type>java.lang.String</key-type>
    <value-type>com.pany.domain.Client</value-type>
    <expiry>
      <ttl unit="minutes">2</ttl>
    </expiry>
    <heap unit="entries">2000</heap>
  </cache-template>

  <cache-template name="tinyCache">
    <heap unit="entries">20</heap>
  </cache-template>

  <cache-template name="byRefTemplate">
    <key-type copier="org.ehcache.impl.copy.IdentityCopier">java.lang.Long</key-type>
    <value-type copier="org.ehcache.impl.copy.IdentityCopier">com.pany.domain.Client</value-type>
    <heap unit="entries">10</heap>
  </cache-template>

  <cache-template name="byValueTemplate">
    <key-type copier="org.ehcache.impl.copy.SerializingCopier">java.lang.Long</key-type>
    <value-type copier="org.ehcache.impl.copy.SerializingCopier">com.pany.domain.Client</value-type>
    <heap unit="entries">10</heap>
  </cache-template>

  <cache-template name="mixedTemplate1">
    <key-type copier="org.ehcache.impl.copy.IdentityCopier">java.lang.Long</key-type>
    <value-type copier="org.ehcache.impl.copy.SerializingCopier">com.pany.domain.Client</value-type>
    <heap unit="entries">10</heap>
  </cache-template>

  <cache-template name="mixedTemplate2">
    <key-type copier="org.ehcache.impl.copy.SerializingCopier">java.lang.Long</key-type>
    <value-type copier="org.ehcache.impl.copy.IdentityCopier">com.pany.domain.Client</value-type>
    <heap unit="entries">10</heap>
  </cache-template>
</config>
1 First, declare a namespace for the JCache extension, e.g. jsr107.
2 Within a service element at the top of your configuration, add a jsr107:defaults element.
3 The element takes an optional attribute default-template, which references the cache-template to use for all javax.cache.Cache elements created by the application at runtime using javax.cache.CacheManager.createCache. In this example, the default cache-template used will be tinyCache, meaning that in addition to their particular configuration, any programmatically created Cache instances will have their capacity constrained to 20 entries.
4 Nested within the jsr107:defaults element, add specific cache-templates to use for the given named Cache. So, for example, when creating the Cache named foos at runtime, Ehcache will enhance its configuration, giving it a capacity of 2000 entries, as well as ensuring that both key and value types are String.
The XSD schema definitions that describe the syntax used for the Ehcache XML configuration are referenced at Ehcache XSDs.

Using the above configuration, you can not only supplement but also override the configuration of JCache-created caches without modifying the application code.

MutableConfiguration<Long, Client> mutableConfiguration = new MutableConfiguration<>();
mutableConfiguration.setTypes(Long.class, Client.class); (1)

Cache<Long, Client> anyCache = manager.createCache("anyCache", mutableConfiguration); (2)

CacheRuntimeConfiguration<Long, Client> ehcacheConfig = (CacheRuntimeConfiguration<Long, Client>)anyCache.getConfiguration(
    Eh107Configuration.class).unwrap(CacheRuntimeConfiguration.class); (3)
ehcacheConfig.getResourcePools().getPoolForResource(ResourceType.Core.HEAP).getSize(); (4)

Cache<Long, Client> anotherCache = manager.createCache("byRefCache", mutableConfiguration);
assertFalse(anotherCache.getConfiguration(Configuration.class).isStoreByValue()); (5)

MutableConfiguration<String, Client> otherConfiguration = new MutableConfiguration<>();
otherConfiguration.setTypes(String.class, Client.class);
otherConfiguration.setExpiryPolicyFactory(CreatedExpiryPolicy.factoryOf(Duration.ONE_MINUTE)); (6)

Cache<String, Client> foosCache = manager.createCache("foos", otherConfiguration);(7)
CacheRuntimeConfiguration<Long, Client> foosEhcacheConfig = (CacheRuntimeConfiguration<Long, Client>)foosCache.getConfiguration(
    Eh107Configuration.class).unwrap(CacheRuntimeConfiguration.class);
Client client1 = new Client("client1", 1);
foosEhcacheConfig.getExpiryPolicy().getExpiryForCreation(42L, client1).toMinutes(); (8)

CompleteConfiguration<String, String> foosConfig = foosCache.getConfiguration(CompleteConfiguration.class);

try {
  final Factory<ExpiryPolicy> expiryPolicyFactory = foosConfig.getExpiryPolicyFactory();
  ExpiryPolicy expiryPolicy = expiryPolicyFactory.create(); (9)
  throw new AssertionError("Expected UnsupportedOperationException");
} catch (UnsupportedOperationException e) {
  // Expected
}
1 Assume existing JCache configuration code, which is store-by-value by default
2 …​ that creates JCache Cache.
3 If you were to get to the Ehcache RuntimeConfiguration
4 …​ you could verify that the template configured capacity is applied to the cache and returns 20 here.
5 The cache template will override the JCache cache’s store-by-value configuration to store-by-reference, since the byRefTemplate that is used to create the cache is configured explicitly using IdentityCopier.
6 Templates will also override the JCache configuration, in this case using a configuration with Time to Live (TTL) 1 minute.
7 Create a cache where the template sets the TTL to 2 minutes.
8 And we can indeed verify that the configuration provided in the template has been applied; the duration will be 2 minutes and not 1 minute.
9 One drawback of this is that when getting at the CompleteConfiguration, you no longer have access to the factories from JCache.

As mentioned in step 5, in order to override the store-by-value configuration of a JCache cache using templates, you can explicitly configure the template using IdentityCopier. But the usage of IdentityCopier is not mandatory to get a store-by-reference cache. You can use any custom copier implementation that does not perform any copying but returns the exact same reference that gets passed into the copy methods. IdentityCopier is just an example that we have provided for your convenience.

Differences in Default Behavior between Ehcache and Ehcache through JCache

Ehcache used natively and Ehcache used through JCache do not always agree on default behavior. While native Ehcache can behave the way JCache specifies, depending on the used configuration mechanism, you may see differences in defaults.

by-reference or by-value

Ehcache and Ehcache through JCache disagree on the default mode for heap-only caching.

Ehcache configuration with JCache MutableConfiguration

Unless you invoke MutableConfiguration.setStoreByValue(boolean), the default value is true. This means that you will be limited to Serializable keys and values when using Ehcache.

Under the cover, this will trigger the use of serializing copiers and pick the appropriate serializer from the default ones.

Ehcache configuration with native XML or code

  • Heap only: When using heap only caches, the default is by-reference unless you configure a Copier.

  • Other tiering configuration: When using any other tiers, since serialization comes into play the default is by-value.

See the sections Serializers and Copiers for related information.

Cache-through and compare-and-swap operations

Ehcache and Ehcache through JCache disagree on the role of the cache loader for compare-and-swap operations.

Ehcache through JCache behaviour

When using compare-and-swap operations, such as putIfAbsent(K, V), the cache loader will not be used if the cache has no mapping present. If the putIfAbsent(K, V) succeeds then the cache writer will be used to propagate the update to the system of record. This could result in the cache behaving like INSERT but effectively causing a blind update on the underlying system of record.

Native Ehcache behaviour

The CacheLoaderWriter will always be used to load missing mappings with and to write updates. This enables the putIfAbsent(K, V) in cache-through to behave as an INSERT on the system of record.

If you need Ehcache through JCache behaviour, the following shows the relevant XML configuration:

<service>
  <jsr107:defaults jsr-107-compliant-atomics="true"/>
</service>