Skip to main content
Caffeine provides a complete JSR-107 (JCache) implementation, allowing you to use standard Java caching APIs backed by Caffeine’s high-performance cache engine.

Installation

Add the JCache module to your project:
implementation 'com.github.ben-manes.caffeine:jcache:3.2.3'
The JCache module requires Java 11 or higher. For Java 8, use version 2.x.

Basic Usage

Caffeine’s JCache provider is automatically discovered through the standard ServiceLoader mechanism:
import javax.cache.Caching;
import javax.cache.Cache;
import javax.cache.CacheManager;
import javax.cache.configuration.MutableConfiguration;

// Get the caching provider
CacheManager cacheManager = Caching.getCachingProvider()
    .getCacheManager();

// Create a cache with a simple configuration
MutableConfiguration<String, Integer> configuration =
    new MutableConfiguration<String, Integer>()
        .setTypes(String.class, Integer.class)
        .setStoreByValue(false)
        .setStatisticsEnabled(true);

Cache<String, Integer> cache = cacheManager.createCache("simpleCache", configuration);

// Use the cache
cache.put("key", 100);
Integer value = cache.get("key");

Caffeine-Specific Configuration

Caffeine extends the standard JCache configuration with additional features through CaffeineConfiguration:
import com.github.benmanes.caffeine.jcache.configuration.CaffeineConfiguration;
import java.util.OptionalLong;
import java.util.concurrent.TimeUnit;

var config = new CaffeineConfiguration<String, String>()
    // Standard JCache settings
    .setTypes(String.class, String.class)
    .setStoreByValue(false)
    .setStatisticsEnabled(true)
    .setManagementEnabled(true)
    
    // Caffeine-specific: size-based eviction
    .setMaximumSize(OptionalLong.of(10_000))
    
    // Caffeine-specific: time-based expiration
    .setExpireAfterWrite(OptionalLong.of(TimeUnit.MINUTES.toNanos(5)))
    .setExpireAfterAccess(OptionalLong.of(TimeUnit.MINUTES.toNanos(10)))
    
    // Caffeine-specific: refresh after write
    .setRefreshAfterWrite(OptionalLong.of(TimeUnit.MINUTES.toNanos(1)));

Cache<String, String> cache = cacheManager.createCache("caffeineCache", config);
1

Define Cache Types

Use setTypes() to specify the key and value types for type safety.
2

Configure Eviction

Set maximumSize or maximumWeight (with a Weigher) for size-based eviction.
3

Configure Expiration

Use expireAfterWrite and expireAfterAccess for time-based expiration.
4

Enable Monitoring

Set statisticsEnabled and managementEnabled for JMX monitoring.

Configuration File

Caffeine supports external configuration using Typesafe Config (HOCON format):
caffeine.jcache {
  # Default settings for all caches
  default {
    policy.maximum.size = 500
  }
  
  # Named cache configuration
  user-cache {
    key-type = java.lang.String
    value-type = com.example.User
    
    store-by-value.enabled = false
    
    read-through {
      enabled = true
      loader = com.example.UserCacheLoader
    }
    
    write-through {
      enabled = true
      writer = com.example.UserCacheWriter
    }
    
    monitoring {
      statistics = true
      management = true
    }
    
    policy {
      maximum.size = 10000
      
      eager-expiration {
        after-write = 5m
        after-access = 10m
      }
      
      refresh.after-write = 1m
    }
    
    listeners = [
      {
        class = com.example.UserEventListener
        filter = com.example.UserEventFilter
        synchronous = false
        old-value-required = false
      }
    ]
  }
}

Read-Through and Write-Through

Implement cache loaders and writers for integration with data sources:
import javax.cache.integration.CacheLoader;
import javax.cache.integration.CacheWriter;

public class DatabaseCacheLoader implements CacheLoader<String, User> {
    private final DataSource dataSource;
    
    @Override
    public User load(String key) throws CacheLoaderException {
        // Load from database
        return dataSource.findUserById(key);
    }
    
    @Override
    public Map<String, User> loadAll(Iterable<? extends String> keys) {
        // Bulk load from database
        return dataSource.findUsersByIds(keys);
    }
}

public class DatabaseCacheWriter implements CacheWriter<String, User> {
    private final DataSource dataSource;
    
    @Override
    public void write(Cache.Entry<? extends String, ? extends User> entry) {
        dataSource.save(entry.getValue());
    }
    
    @Override
    public void delete(Object key) {
        dataSource.delete((String) key);
    }
}
import javax.cache.configuration.FactoryBuilder;

var config = new CaffeineConfiguration<String, User>()
    .setReadThrough(true)
    .setCacheLoaderFactory(FactoryBuilder.factoryOf(new DatabaseCacheLoader(dataSource)))
    .setWriteThrough(true)
    .setCacheWriterFactory(FactoryBuilder.factoryOf(new DatabaseCacheWriter(dataSource)));

Cache<String, User> cache = cacheManager.createCache("userCache", config);

// Automatically loads from database on cache miss
User user = cache.get("user123");

// Automatically writes to database
cache.put("user123", updatedUser);

Entry Processors

Use entry processors for atomic cache operations:
import javax.cache.processor.EntryProcessor;
import javax.cache.processor.MutableEntry;

public class IncrementProcessor implements EntryProcessor<String, Integer, Integer> {
    private final int delta;
    
    public IncrementProcessor(int delta) {
        this.delta = delta;
    }
    
    @Override
    public Integer process(MutableEntry<String, Integer> entry, Object... arguments) {
        Integer current = entry.exists() ? entry.getValue() : 0;
        Integer updated = current + delta;
        entry.setValue(updated);
        return updated;
    }
}

// Use the processor
Integer result = cache.invoke("counter", new IncrementProcessor(1));

Event Listeners

Register listeners to react to cache events:
import javax.cache.event.*;

public class CacheEventLogger implements CacheEntryCreatedListener<String, User>,
                                         CacheEntryUpdatedListener<String, User>,
                                         CacheEntryRemovedListener<String, User>,
                                         CacheEntryExpiredListener<String, User> {
    @Override
    public void onCreated(Iterable<CacheEntryEvent<? extends String, ? extends User>> events) {
        events.forEach(event -> System.out.println("Created: " + event.getKey()));
    }
    
    @Override
    public void onUpdated(Iterable<CacheEntryEvent<? extends String, ? extends User>> events) {
        events.forEach(event -> System.out.println("Updated: " + event.getKey()));
    }
    
    @Override
    public void onRemoved(Iterable<CacheEntryEvent<? extends String, ? extends User>> events) {
        events.forEach(event -> System.out.println("Removed: " + event.getKey()));
    }
    
    @Override
    public void onExpired(Iterable<CacheEntryEvent<? extends String, ? extends User>> events) {
        events.forEach(event -> System.out.println("Expired: " + event.getKey()));
    }
}

// Register the listener
MutableCacheEntryListenerConfiguration<String, User> listenerConfig =
    new MutableCacheEntryListenerConfiguration<>(
        FactoryBuilder.factoryOf(new CacheEventLogger()),
        null, // no filter
        false, // not old value required
        true   // synchronous
    );

cache.registerCacheEntryListener(listenerConfig);

JMX Monitoring

Enable JMX for runtime monitoring and management:
var config = new CaffeineConfiguration<String, String>()
    .setStatisticsEnabled(true)
    .setManagementEnabled(true);

Cache<String, String> cache = cacheManager.createCache("monitoredCache", config);

// Access statistics
CacheStatisticsMXBean stats = 
    JMX.newMXBeanProxy(ManagementFactory.getPlatformMBeanServer(),
        new ObjectName("javax.cache:type=CacheStatistics,Cache=monitoredCache"),
        CacheStatisticsMXBean.class);

System.out.println("Hit rate: " + stats.getCacheHitPercentage());
System.out.println("Hits: " + stats.getCacheHits());
System.out.println("Misses: " + stats.getCacheMisses());

Weighted Eviction

Use custom weighers for weighted size-based eviction:
import com.github.benmanes.caffeine.cache.Weigher;
import javax.cache.configuration.Factory;

public class UserWeigher implements Weigher<String, User> {
    @Override
    public int weigh(String key, User user) {
        return user.getEmailList().size() + user.getPreferences().size();
    }
}

var config = new CaffeineConfiguration<String, User>()
    .setMaximumWeight(OptionalLong.of(100_000))
    .setWeigherFactory(Optional.of(() -> new UserWeigher()));

API Mapping

Common JSR-107 operations and their Caffeine equivalents:
JCache APIDescriptionCaffeine Equivalent
get(key)Get a valuecache.getIfPresent(key)
put(key, value)Put a valuecache.put(key, value)
getAndPut(key, value)Get and replacecache.asMap().put(key, value)
putIfAbsent(key, value)Conditional putcache.asMap().putIfAbsent(key, value)
remove(key)Remove entrycache.invalidate(key)
replace(key, value)Replace if existscache.asMap().replace(key, value)
clear()Clear cachecache.invalidateAll()
invoke()Entry processorN/A (JCache-specific)
Store-by-value is disabled by default in Caffeine’s JCache implementation for better performance. Enable it with setStoreByValue(true) if you need defensive copying.

Common Use Cases

Database Query Cache

var config = new CaffeineConfiguration<String, ResultSet>()
    .setMaximumSize(OptionalLong.of(1000))
    .setExpireAfterWrite(OptionalLong.of(TimeUnit.MINUTES.toNanos(15)))
    .setReadThrough(true)
    .setCacheLoaderFactory(() -> new QueryCacheLoader(database));

Cache<String, ResultSet> queryCache = cacheManager.createCache("queries", config);

Session Store

var config = new CaffeineConfiguration<String, Session>()
    .setExpireAfterAccess(OptionalLong.of(TimeUnit.MINUTES.toNanos(30)))
    .setMaximumSize(OptionalLong.of(10_000))
    .setWriteThrough(true)
    .setCacheWriterFactory(() -> new SessionWriter(redis));

Cache<String, Session> sessions = cacheManager.createCache("sessions", config);

API Response Cache

var config = new CaffeineConfiguration<String, ApiResponse>()
    .setMaximumSize(OptionalLong.of(5000))
    .setExpireAfterWrite(OptionalLong.of(TimeUnit.SECONDS.toNanos(60)))
    .setRefreshAfterWrite(OptionalLong.of(TimeUnit.SECONDS.toNanos(30)))
    .setStatisticsEnabled(true);

Cache<String, ApiResponse> apiCache = cacheManager.createCache("api", config);

Build docs developers (and LLMs) love