Last updatedMay 15, 2019

How do I cache data in a plugin?

Confluence includes a caching API atlassian-cache that should be used instead of custom cache solutions.

The provided API:

  • Ensures proper expiry of cache data (default expiry: 1 hour).
  • Enables monitoring of cache usage by administrators.
  • Functions correctly in a Confluence cluster.

This page describes how to use the atlassian-cache APIs.

An example of atlassian-cache APIs usage

The following code block is a short example of some code using the atlassian-cache APIs.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import org.apache.commons.lang3.StringUtils;
import com.atlassian.cache.Cache;
import com.atlassian.cache.CacheLoader;
import com.atlassian.cache.CacheManager;
import com.atlassian.cache.CacheSettingsBuilder;
import com.atlassian.plugin.spring.scanner.annotation.component.Scanned;
import com.atlassian.plugin.spring.scanner.annotation.imports.ConfluenceImport;
import javax.annotation.Nonnull;
// some imports omitted for clarity

 @Scanned
public class ListPagesMacro implements Macro {
    private final Cache<String, String> cache;

    @ConfluenceImport
    private final PageManager pageManager;

    public ListPagesMacro(@ConfluenceImport CacheManager cacheManager, PageManager pageManager) {
        this.pageManager = pageManager;
        cache = cacheManager.getCache(ListPagesMacro.class.getName() + ".cache",
                new ListPagesCacheLoader(),
                new CacheSettingsBuilder().expireAfterAccess(30, TimeUnit.MINUTES).build());    
    }

    @Override
    public String execute(Map<String, String> parameters, String body, ConversionContext context)
            throws MacroExecutionException {
        String spaceKey = parameters.get("spaceKey");
        if (StringUtils.isBlank(spaceKey))
            spaceKey = context.getSpaceKey();
        if (StringUtils.isBlank(spaceKey))
            throw new MacroExecutionException("A spaceKey is required.");


        // if the spaceKey is not present in the cache then the ListPagesCacheLoader will create the value as
        // implemented in the 'load' method and it will be populated into the cache automatically.
        return cache.get(spaceKey);
    }

    // The loader class used to populate a cache entry on cache miss.
    private class ListPagesCacheLoader implements CacheLoader<String, String> {
        @Override
        public String load(@Nonnull String spaceKey) {
            return renderPageList(spaceKey);
        }
    }

    String renderPageList(String spaceKey) {
        // implementation goes here
    }
    ...
}

Instructions for API usage

  1. To use the Atlassian Cache API, you first need to get a CacheManager injected into your component (macro, action, etc.). To do this, add a setter or constructor parameter to your component, depending on whether you are using setter-based or constructor-based dependency injection.

  2. To retrieve a cache from the cache manager, use a cache key that is unique to your plugin. We recommend using the fully qualified name of the class that uses the cache, plus a name that describes the contents of the cache. The returned Cache has an API very similar to Java Map.

  3. To store a value in the cache, call put(Object, Object) and get(Object) to look up a previously stored value.

In a single instance of Confluence, you can store any objects in the cache. In a clustered instance, you can only store keys and values that implement Serializable.

Always depend on CacheManager instead of its super-interface CacheFactory. CacheFactory exported by Confluence is an instance of TransactionalCacheFactory with often surprising performance characteristics.

Cache configuration

If you don't specify a cache configuration explicitly, then it is determined by Confluence by default, with the ability for the Confluence administrator to change the settings at runtime.

The default expiry is one hour and the cache will store up to 1000 items. If space is needed for new items, the least-recently used items will be automatically expired or removed. However, it is preferable to create a cache specifically configured to the plugin requirements.

To avoid consuming excessive amounts of memory, you should always try and set an expiry policy on the caches you create. A timed base expiry is most appropriate for most applications. It is usually very difficult to determine an appropriate number of cache entries that suits clients at all scales.