JCache overview

The JCache (aka JSR-107) specification defines the standard caching API for Java. The specification was developed under the Java Community Process v2.9 by an expert group including members from the Ehcache developer community. JCache provides a very simple API set that is easy to use and vendor neutral.

Being one of the pioneers in the Java caching domain, Ehcache had to offer an implementation that is fully compliant with the JCache specification.

For years, the biggest problem that application developers have faced while wanting to try cache implementations by different vendors is the stark contrast in the APIs offered by these vendors. Developers were forced to rewrite a lot of their caching related code in an application just to try out a new caching solution. This leads to developers sticking with what they had, as the bar to investigating other products was too high.

The availability of the JCache specification gives real added value for developers as there is now a standard caching API they can use. So it is easier for an application developer to switch between products by different vendors and choose the one that suits them best without changing a single line of their application code interacting with caches. All they have to do is swap the caching library from one vendor with another. Unless they use vendor specific APIs or configurations, this swap will be transparent.

This document covers the basic usage of JCache API with Ehcache’s implementation. Complete details on the JCache specification can be found here.

Getting started with Ehcache 3 & JCache

See the section on using Ehcache 3 and JSR-107 for details on required libraries.

In addition to the Cache interface, JCache specification has defined two more 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 some sample code 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 only 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.

JSR-107 and Ehcache configuration integration

As mentioned already, the JCache specification offers a minimal set of configurations 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 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 section.

Starting from JSR-107 created caches

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

MutableConfiguration<Long, String> configuration = new MutableConfiguration<Long, String>();
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 JSR-107 cache using the MutableConfiguration from the specification
2 Get to the JSR-107 CompleteConfiguration
3 Get to the Ehcache JSR-107 configuration bridge
4 Unwrap to the Ehcache CacheRuntimeConfiguration type

Building the configuration using Ehcache APIs

CacheManager level configuration

If you need to configure features at the CacheManager level, like 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 in parameter.

Cache level configuration

You can also create a JSR-107 Cache using an Ehcache CacheConfiguration. When using this mechanism, no JSR-107 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 - through a builder as shown here or alternatively use an XML configuration (as described in the following section).
2 Use the configuration with JSR-107 API by wrapping it
3 Get back to the Ehcache CacheConfiguration …​
4 or even to the runtime configuration.
5 No JSR-107 CompleteConfiguration is available in this context

Getting JSR-107 caches configured through Ehcache XML

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

Find below the XML configuration followed by the code to use it from JSR-107:

<config
    xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
    xmlns='http://www.ehcache.org/v3'
    xsi:schemaLocation="
        http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core-3.0.xsd">

  <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>
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 Invoking javax.cache.spi.CachingProvider.getCacheManager(java.net.URI, java.lang.ClassLoader)
2 and passing in a URI that resolves to an Ehcache XLM configuration file.
3 the second argument being 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 also use the CachingProvider.getCacheManager() method that takes no arguments instead. The URI and ClassLoader used to configure the CacheManager will then use the vendor specific values returned by CachingProvider.getDefaultURI and .getDefaultClassLoader respectively.

Controlling JSR-107 MBeans from XML

When using Ehcache XML, you may want to enable management and / or statistics MBeans for JSR-107 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:xsi='http://www.w3.org/2001/XMLSchema-instance'
    xmlns='http://www.ehcache.org/v3'
    xmlns:jsr107='http://www.ehcache.org/v3/jsr107'
    xsi:schemaLocation="
        http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core-3.0.xsd
        http://www.ehcache.org/v3/jsr107 http://www.ehcache.org/schema/ehcache-107-ext-3.0.xsd">


  <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 JSR-107 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, while the management one remains enabled according to the service configuration

Supplement JSR-107’s configurations

You can also create cache-templates, see the Cache Templates section of the XML Documentation for more details. The Ehcache 3 JSR-107 Caching Provider comes with an extension to the regular XML configuration so you can:

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

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

This feature is particularly useful to configure Cache beyond the JSR-107 specification, for example, giving Cache a capacity constraint. All that’s needed is adding a jsr107 service in your XML configuration file:

<config
    xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
    xmlns='http://www.ehcache.org/v3'
    xmlns:jsr107='http://www.ehcache.org/v3/jsr107'
    xsi:schemaLocation="
        http://www.ehcache.org/v3 http://www.ehcache.org/schema/ehcache-core-3.0.xsd
        http://www.ehcache.org/v3/jsr107 http://www.ehcache.org/schema/ehcache-107-ext-3.0.xsd"> (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 107 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 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, programmatically created Cache instances will have their capacity constrained to 20 entries.
4 Nested within the jsr107:defaults, 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 config, giving it a capacity of 2000 entries, as well as insuring both key and value types are String.
See this xsd for a complete definition

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

MutableConfiguration<Long, Client> mutableConfiguration = new MutableConfiguration<Long, Client>();
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<String, Client>();
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.getExpiry().getExpiryForCreation(42L, client1).getLength(); (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 JSR-107 configuration code, which is store-by-value by default
2 that creates JSR-107 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 JSR-107 cache’s store-by-value config to store-by-ref since the byRefTemplate template that is used to create the cache is configured explicitly using IdentityCopier.
6 Templates will also override the JSR-107 configuration, in this case using a configuration with TTL 1 minute
7 used to 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.
9 One drawback of this is that when getting at the CompleteConfiguration, you no longer have access to the factories from JSR-107.
As mentioned in step 5, in order to override store-by-value configuration of a JSR-107 cache using templates you can explicitly configure the template using IdentityCopier. But the usage of IdentityCopier is not mandatory to get a store-by-ref 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.

A word on defaults

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

by-reference or by-value

Native Ehcache 3 and Ehcache 3 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 3.

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 documentation dedicated to serializers and copiers for more information.

Cache-through and compare-and-swap operations

Native Ehcache 3 and Ehcache 3 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 3 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>