KeyValueRepository
Since Camel 4.23
The KeyValueRepository SPI (org.apache.camel.spi.KeyValueRepository) provides a unified key-value abstraction that can be backed by any storage technology. Instead of implementing separate repository interfaces for each storage backend, a single KeyValueRepository implementation can serve as the backing store for multiple Camel patterns:
-
Idempotent Consumer — via
KeyValueIdempotentRepository -
Aggregator — via
KeyValueAggregationRepository -
Cache EIP — uses
KeyValueRepositorydirectly as cache backend -
State Store component — key-value operations in routes
This means you can configure a single backend (e.g. Redis) and use it across all four patterns without duplicating configuration or managing separate repository instances.
API
The KeyValueRepository interface extends Service and defines the following operations:
| Method | Description |
|---|---|
| Retrieve the value for a key (null if absent or expired) |
| Store a value with optional TTL; returns the previous value |
| Remove and return the value for a key |
| Check if a non-expired entry exists |
| Return all non-expired keys |
| Remove all entries |
| Count non-expired entries |
| Store only if key is absent; returns existing value or null |
| Compare-and-swap: replace if current value matches expected |
| Compare-and-swap: delete only if current value matches expected |
TTL is specified as java.time.Duration. A null, zero, or negative duration means no expiration.
The putIfAbsent, replace, and delete(key, expected) methods have default implementations that are not atomic. Backends that support native atomic operations override these for better concurrency guarantees — see the atomicity column in the table below.
Available Backends
Camel provides the following KeyValueRepository implementations out of the box:
| Backend | Class | Module | TTL | Atomic CAS |
|---|---|---|---|---|
Memory (default) |
|
| Lazy eviction | Yes ( |
JDBC |
|
|
| No (default) |
JPA |
|
|
| No (default) |
Cassandra |
|
| Native | No (default) |
Kafka |
|
| Envelope timestamp | No (default) |
Caffeine |
|
| Native per-entry | Yes ( |
Ehcache |
|
| Lazy eviction | No (default) |
JCache (JSR-107) |
|
| Lazy eviction | No (default) |
Hazelcast |
|
| Native per-entry | Yes ( |
Redis |
|
| Native key expiry | Partial (CAS yes, CAS+TTL not atomic) |
Infinispan |
|
| Native lifespan | Yes (fully atomic) |
Memory (default)
The default backend stores entries in a ConcurrentHashMap with lazy TTL eviction. No additional dependency is needed.
KeyValueRepository repo = new MemoryKeyValueRepository(); JDBC
Stores entries in a relational database table via plain JDBC.
JdbcKeyValueRepository repo = new JdbcKeyValueRepository();
repo.setDataSource(myDataSource);
repo.setTableName("camel_kvr"); // optional, defaults to "camel_kvr" The table is auto-created on startup if it does not exist. TTL is stored as an expires_at epoch-millis column; expired entries are cleaned lazily on read.
JPA
Stores entries using JPA with an EntityManager.
JpaKeyValueRepository repo = new JpaKeyValueRepository();
repo.setEntityManagerFactory(myEmf); Uses a KeyValueEntry entity mapped to a camel_kvr table. Add the entity to your persistence.xml persistence unit.
Cassandra
Stores entries in Apache Cassandra using the DataStax driver.
CassandraKeyValueRepository repo = new CassandraKeyValueRepository();
repo.setSession(cassandraSession);
repo.setTableName("camel_kvr"); // optional TTL uses Cassandra’s native USING TTL (converted from Duration to seconds). The table is auto-created on startup.
Kafka
Uses a Kafka compacted topic as a persistent key-value store.
KafkaKeyValueRepository repo = new KafkaKeyValueRepository();
repo.setBootstrapServers("localhost:9092");
repo.setTopic("camel-kvr"); // optional Entries are replayed from the topic on startup into an in-memory ConcurrentHashMap cache. TTL is stored as an epoch-millis timestamp in the value envelope.
Caffeine
In-process cache with native per-entry TTL via Caffeine’s Expiry API.
CaffeineKeyValueRepository repo = new CaffeineKeyValueRepository();
repo.setMaximumSize(10000); // optional, defaults to 10,000 Atomic CAS operations via cache.asMap().computeIfPresent().
Ehcache
Uses an Ehcache 3 CacheManager.
EhcacheKeyValueRepository repo = new EhcacheKeyValueRepository();
repo.setCacheManager(myCacheManager);
repo.setCacheName("camel-kvr"); TTL is managed via a wrapper with lazy eviction on read (Ehcache 3 does not support per-entry TTL natively).
JCache (JSR-107)
Works with any JCache provider (Hazelcast, Ehcache, etc.).
JCacheKeyValueRepository repo = new JCacheKeyValueRepository();
repo.setCachingProvider(myProvider);
repo.setCacheName("camel-kvr"); Hazelcast
Distributed key-value store using Hazelcast IMap.
HazelcastKeyValueRepository repo = new HazelcastKeyValueRepository();
repo.setHazelcastInstance(myHzInstance);
repo.setMapName("camel-kvr"); // optional Supports native per-entry TTL and atomic CAS via IMap.replace(K, V, V).
Redis
Backed by the Redisson client.
RedisKeyValueRepository repo = new RedisKeyValueRepository("localhost:6379");
repo.setKeyPrefix("camel-kvr:"); // optional, defaults to "camel-kvr:" Atomic CAS via RBucket.compareAndSet(). Note: when using replace() with a TTL, the CAS and TTL are applied as two separate Redis calls — there is a brief window where the new value exists without its TTL.
Usage with Camel Patterns
Single backend, multiple patterns
Register a single KeyValueRepository in the Camel registry and all patterns auto-discover it:
@BindToRegistry("kvRepo")
public KeyValueRepository kvRepo() {
return new RedisKeyValueRepository("localhost:6379");
} With this single bean in the registry:
-
The State Store component auto-discovers and uses it for key-value operations.
-
The Idempotent Consumer EIP auto-discovers and wraps it in a
KeyValueIdempotentRepository. -
The Aggregator EIP auto-discovers and wraps it in a
KeyValueAggregationRepository. -
The Cache EIP uses it as the cache backend.
No explicit wiring is needed.
Explicit wiring
You can also wire the backend explicitly when you need more control:
// Idempotent Consumer
KeyValueRepository store = new JdbcKeyValueRepository(myDataSource);
IdempotentRepository idempotent = new KeyValueIdempotentRepository(store);
// Aggregation Repository
AggregationRepository aggregation = new KeyValueAggregationRepository(store); XML / YAML DSL
<bean name="kvStore" type="org.apache.camel.component.redis.RedisKeyValueRepository">
<property name="endpoint" value="localhost:6379"/>
</bean>
<!-- Idempotent Consumer backed by Redis -->
<bean name="idempotentRepo" type="org.apache.camel.support.KeyValueIdempotentRepository">
<constructors>
<constructor value="#kvStore"/>
</constructors>
</bean> Writing a Custom Backend
Implement the KeyValueRepository interface to create your own backend:
public class MyCustomRepository extends ServiceSupport implements KeyValueRepository {
@Override
public Object get(String key) { /* ... */ }
@Override
public Object put(String key, Object value, Duration ttl) { /* ... */ }
@Override
public Object delete(String key) { /* ... */ }
@Override
public boolean contains(String key) { /* ... */ }
@Override
public Set<String> keys() { /* ... */ }
@Override
public void clear() { /* ... */ }
} Override putIfAbsent, replace, and delete(key, expected) if your store supports native atomic operations for better concurrency.