0
# Asynchronous Caching
1
2
Caffeine provides asynchronous cache interfaces that return `CompletableFuture` instances for non-blocking cache operations. The async interfaces enable high-throughput applications to perform cache operations without blocking threads.
3
4
## AsyncCache Interface
5
6
The `AsyncCache` interface provides asynchronous cache operations returning futures.
7
8
```java { .api }
9
public interface AsyncCache<K, V> {
10
// Retrieval operations
11
CompletableFuture<V> getIfPresent(K key);
12
CompletableFuture<V> get(K key, Function<? super K, ? extends V> mappingFunction);
13
CompletableFuture<V> get(K key, BiFunction<? super K, ? super Executor, CompletableFuture<V>> mappingFunction);
14
CompletableFuture<Map<K, V>> getAll(Iterable<? extends K> keys, Function<Set<? extends K>, Map<K, V>> mappingFunction);
15
16
// Storage operations
17
void put(K key, CompletableFuture<V> valueFuture);
18
19
// Synchronous view
20
Cache<K, V> synchronous();
21
}
22
```
23
24
### Basic Async Operations
25
26
#### Asynchronous Retrieval
27
28
```java
29
AsyncCache<String, String> asyncCache = Caffeine.newBuilder()
30
.maximumSize(1000)
31
.buildAsync();
32
33
// Simple async retrieval - returns future of value or null
34
CompletableFuture<String> future1 = asyncCache.getIfPresent("key1");
35
String value = future1.join(); // null if not present
36
37
// Async get with compute function
38
CompletableFuture<String> future2 = asyncCache.get("key2", k -> {
39
// This computation runs asynchronously
40
return "computed_" + k;
41
});
42
43
// Async get with executor-aware compute function
44
CompletableFuture<String> future3 = asyncCache.get("key3", (key, executor) -> {
45
return CompletableFuture.supplyAsync(() -> {
46
// Long-running computation on provided executor
47
try {
48
Thread.sleep(1000);
49
} catch (InterruptedException e) {
50
Thread.currentThread().interrupt();
51
}
52
return "async_computed_" + key;
53
}, executor);
54
});
55
```
56
57
#### Asynchronous Storage
58
59
```java
60
// Store a completed future
61
asyncCache.put("immediate", CompletableFuture.completedFuture("immediate_value"));
62
63
// Store a future that completes later
64
CompletableFuture<String> laterFuture = CompletableFuture.supplyAsync(() -> {
65
// Simulate async computation
66
try {
67
Thread.sleep(500);
68
} catch (InterruptedException e) {
69
Thread.currentThread().interrupt();
70
}
71
return "delayed_value";
72
});
73
asyncCache.put("delayed", laterFuture);
74
```
75
76
#### Bulk Async Operations
77
78
```java
79
Set<String> keys = Set.of("key1", "key2", "key3");
80
81
// Bulk async retrieval with computation for missing values
82
CompletableFuture<Map<String, String>> bulkFuture = asyncCache.getAll(keys, missingKeys -> {
83
Map<String, String> result = new HashMap<>();
84
for (String key : missingKeys) {
85
result.put(key, "bulk_" + key);
86
}
87
return result;
88
});
89
90
Map<String, String> bulkResult = bulkFuture.join();
91
```
92
93
### Working with Futures
94
95
#### Chaining Operations
96
97
```java
98
AsyncCache<String, UserData> userCache = Caffeine.newBuilder()
99
.maximumSize(1000)
100
.buildAsync();
101
102
// Chain async operations
103
CompletableFuture<String> userNameFuture = userCache
104
.get("user123", userId -> fetchUserFromDatabase(userId))
105
.thenApply(userData -> userData.getName())
106
.thenApply(String::toUpperCase);
107
108
// Handle results asynchronously
109
userNameFuture.thenAccept(userName -> {
110
System.out.println("User name: " + userName);
111
}).exceptionally(throwable -> {
112
System.err.println("Failed to get user name: " + throwable.getMessage());
113
return null;
114
});
115
```
116
117
#### Combining Multiple Cache Operations
118
119
```java
120
CompletableFuture<String> user1Future = asyncCache.get("user1", this::loadUser);
121
CompletableFuture<String> user2Future = asyncCache.get("user2", this::loadUser);
122
123
// Combine results from multiple async cache operations
124
CompletableFuture<String> combinedFuture = user1Future.thenCombine(user2Future, (user1, user2) -> {
125
return "Combined: " + user1 + " + " + user2;
126
});
127
128
String combined = combinedFuture.join();
129
```
130
131
## AsyncLoadingCache Interface
132
133
The `AsyncLoadingCache` interface extends `AsyncCache` and provides automatic async loading.
134
135
```java { .api }
136
public interface AsyncLoadingCache<K, V> extends AsyncCache<K, V> {
137
// Automatic async loading
138
CompletableFuture<V> get(K key);
139
CompletableFuture<Map<K, V>> getAll(Iterable<? extends K> keys);
140
141
// Synchronous view
142
LoadingCache<K, V> synchronous();
143
}
144
```
145
146
### Async Loading Operations
147
148
#### Creating Async Loading Cache with CacheLoader
149
150
```java
151
AsyncLoadingCache<String, String> asyncLoadingCache = Caffeine.newBuilder()
152
.maximumSize(1000)
153
.buildAsync(key -> {
154
// This CacheLoader runs on the default executor
155
Thread.sleep(100); // Simulate work
156
return "loaded_" + key;
157
});
158
159
// Get value - loads asynchronously if not present
160
CompletableFuture<String> loadedFuture = asyncLoadingCache.get("key1");
161
String loadedValue = loadedFuture.join();
162
```
163
164
#### Creating Async Loading Cache with AsyncCacheLoader
165
166
```java
167
AsyncLoadingCache<String, UserData> userAsyncCache = Caffeine.newBuilder()
168
.maximumSize(1000)
169
.buildAsync(new AsyncCacheLoader<String, UserData>() {
170
@Override
171
public CompletableFuture<UserData> asyncLoad(String userId, Executor executor) {
172
return CompletableFuture.supplyAsync(() -> {
173
// Async loading logic
174
return databaseService.fetchUser(userId);
175
}, executor);
176
}
177
178
@Override
179
public CompletableFuture<Map<String, UserData>> asyncLoadAll(
180
Set<? extends String> userIds, Executor executor) {
181
return CompletableFuture.supplyAsync(() -> {
182
// Efficient bulk async loading
183
return databaseService.fetchUsers(userIds);
184
}, executor);
185
}
186
});
187
188
// Efficient bulk async loading
189
CompletableFuture<Map<String, UserData>> usersFuture =
190
userAsyncCache.getAll(Set.of("user1", "user2", "user3"));
191
```
192
193
### Async Refresh Operations
194
195
```java
196
AsyncLoadingCache<String, String> refreshingAsyncCache = Caffeine.newBuilder()
197
.maximumSize(1000)
198
.refreshAfterWrite(Duration.ofMinutes(5))
199
.buildAsync((key, executor) -> CompletableFuture.supplyAsync(() -> {
200
// Async refresh computation
201
return fetchLatestValue(key);
202
}, executor));
203
204
// The cache will automatically refresh values in the background
205
CompletableFuture<String> valueFuture = refreshingAsyncCache.get("key1");
206
207
// Manual async refresh
208
CompletableFuture<String> refreshFuture = refreshingAsyncCache.synchronous().refresh("key1");
209
```
210
211
## Synchronous Views
212
213
Both async cache interfaces provide synchronous views for mixed usage patterns.
214
215
### AsyncCache Synchronous View
216
217
```java
218
AsyncCache<String, String> asyncCache = Caffeine.newBuilder()
219
.maximumSize(1000)
220
.buildAsync();
221
222
// Get synchronous view
223
Cache<String, String> syncView = asyncCache.synchronous();
224
225
// Use synchronous operations on the same underlying cache
226
asyncCache.put("async_key", CompletableFuture.completedFuture("async_value"));
227
String value = syncView.getIfPresent("async_key"); // "async_value"
228
229
syncView.put("sync_key", "sync_value");
230
CompletableFuture<String> asyncValue = asyncCache.getIfPresent("sync_key");
231
// Future completed with "sync_value"
232
```
233
234
### AsyncLoadingCache Synchronous View
235
236
```java
237
AsyncLoadingCache<String, String> asyncLoadingCache = Caffeine.newBuilder()
238
.maximumSize(1000)
239
.buildAsync(key -> "loaded_" + key);
240
241
// Get synchronous loading view
242
LoadingCache<String, String> syncLoadingView = asyncLoadingCache.synchronous();
243
244
// Mixed usage
245
CompletableFuture<String> asyncLoaded = asyncLoadingCache.get("async_key");
246
String syncLoaded = syncLoadingView.get("sync_key");
247
```
248
249
## Error Handling in Async Caches
250
251
### Handling Computation Failures
252
253
```java
254
AsyncCache<String, String> asyncCache = Caffeine.newBuilder()
255
.maximumSize(1000)
256
.buildAsync();
257
258
CompletableFuture<String> failingFuture = asyncCache.get("error_key", key -> {
259
if (key.equals("error_key")) {
260
throw new RuntimeException("Computation failed");
261
}
262
return "success_" + key;
263
});
264
265
// Handle failures
266
failingFuture
267
.thenApply(value -> "Processed: " + value)
268
.exceptionally(throwable -> {
269
System.err.println("Computation failed: " + throwable.getMessage());
270
return "fallback_value";
271
})
272
.thenAccept(result -> System.out.println("Final result: " + result));
273
```
274
275
### Async Loader Error Handling
276
277
```java
278
AsyncLoadingCache<String, String> asyncLoadingCache = Caffeine.newBuilder()
279
.maximumSize(1000)
280
.buildAsync((key, executor) -> {
281
return CompletableFuture.supplyAsync(() -> {
282
if (key.startsWith("fail_")) {
283
throw new RuntimeException("Loading failed for " + key);
284
}
285
return "loaded_" + key;
286
}, executor);
287
});
288
289
// Failed loads result in exceptional completion
290
CompletableFuture<String> result = asyncLoadingCache.get("fail_test");
291
result.whenComplete((value, throwable) -> {
292
if (throwable != null) {
293
System.err.println("Load failed: " + throwable.getMessage());
294
// Entry is not cached when loading fails
295
} else {
296
System.out.println("Loaded: " + value);
297
}
298
});
299
```
300
301
## Performance Considerations
302
303
### Executor Configuration
304
305
```java
306
// Configure custom executor for async operations
307
ForkJoinPool customPool = new ForkJoinPool(20);
308
309
AsyncCache<String, String> customExecutorCache = Caffeine.newBuilder()
310
.maximumSize(1000)
311
.executor(customPool)
312
.buildAsync();
313
314
// All async computations will use the custom executor
315
CompletableFuture<String> future = customExecutorCache.get("key", k -> expensiveComputation(k));
316
```
317
318
### Memory Usage
319
320
Async caches store `CompletableFuture` instances rather than direct values:
321
322
```java
323
// Memory overhead: each entry stores a CompletableFuture wrapper
324
AsyncCache<String, String> asyncCache = Caffeine.newBuilder()
325
.maximumSize(1000)
326
.buildAsync();
327
328
// For completed values, consider using synchronous cache if memory is constrained
329
Cache<String, String> syncCache = Caffeine.newBuilder()
330
.maximumSize(1000)
331
.build();
332
```
333
334
### Thread Safety
335
336
Async caches maintain the same thread-safety guarantees as synchronous caches:
337
338
```java
339
AsyncCache<String, String> threadSafeAsyncCache = Caffeine.newBuilder()
340
.maximumSize(1000)
341
.buildAsync();
342
343
// Multiple threads can safely access async cache concurrently
344
ExecutorService executor = Executors.newFixedThreadPool(10);
345
List<CompletableFuture<String>> futures = new ArrayList<>();
346
347
for (int i = 0; i < 100; i++) {
348
final int threadId = i;
349
CompletableFuture<String> future = threadSafeAsyncCache.get("key_" + threadId,
350
k -> "computed_" + k);
351
futures.add(future);
352
}
353
354
// Wait for all computations
355
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
356
```