quarkusio/quarkus · error · IllegalArgumentException
Cannot cache `null` value
Error message
Cannot cache `null` value
What it means
RedisCacheImpl does not allow caching null values: when a value loader completes with null, the cache throws IllegalArgumentException instead of storing a null (unlike the local Caffeine cache which permits nulls). The check happens right before marshalling, since null cannot be encoded for Redis storage.
Source
Thrown at extensions/redis-cache/runtime/src/main/java/io/quarkus/cache/redis/runtime/RedisCacheImpl.java:193
return startingPoint
.chain(Unchecked.function(new UncheckedFunction<>() {
@Override
public Uni<V> apply(V cached) throws Exception {
if (cached != null) {
// Unwatch if optimistic locking
if (cacheInfo.useOptimisticLocking) {
return connection.send(Request.cmd(Command.UNWATCH))
.replaceWith(cached);
}
return Uni.createFrom().item(new StaticSupplier<>(cached));
} else {
Uni<V> uni = computeValue(key, valueLoader, isWorkerThread);
return uni.onItem().call(new Function<V, Uni<?>>() {
@Override
public Uni<?> apply(V value) {
if (value == null) {
throw new IllegalArgumentException("Cannot cache `null` value");
}
byte[] encodedValue = marshaller.encode(value);
Uni<V> result;
if (cacheInfo.useOptimisticLocking) {
result = multi(connection, set(connection, encodedKey, encodedValue))
.replaceWith(value);
} else {
result = set(connection, encodedKey, encodedValue).replaceWith(value);
}
if (isWorkerThread) {
return result.runSubscriptionOn(
MutinyHelper.blockingExecutor(vertx.getDelegate(), false));
}
return result;
}
});
}
}View on GitHub (pinned to e1c734241f)
Solutions
- Make the loader return a non-null sentinel or Optional and unwrap after retrieval
- Use getOrNull(...) semantics: store a marker EMPTY value and map back to null on read
- Change the method/logic to not return null (throw a domain-specific 'not found' or return a default)
- Use the local (Caffeine) cache type instead of redis for caches that must allow null values
Example fix
// before V v = cache.get(key, k -> maybeNullLoader(k)); // after Optional<V> opt = cache.get(key, k -> Optional.ofNullable(maybeNullLoader(k))); V v = opt.orElse(null);
Defensive patterns
Strategy: try-catch
Validate before calling
V value = loader.apply(key);
if (value == null) throw new IllegalStateException("Loader must not return null for redis cache"); Try / catch
try {
V v = cache.get(key, loader);
} catch (IllegalArgumentException e) {
if (e.getMessage().contains("Cannot cache `null` value")) {
// treat as cache miss / not-found and fall back
} else throw e;
} Prevention
- Wrap nullable results in Optional before caching
- Use sentinel EMPTY values for redis caches
- Document that redis caches are null-intolerant
When it happens
Trigger: Calling RedisCache.get/getAsync (non-getOrNull variants) with a compute function or value loader that returns null; @CacheResult on a method that returns null; a Uni chain resolving to null item.
Common situations: Database lookups that legitimately return no row; optional data fetched by key; migrating from Caffeine/local cache to Redis cache where null-tolerant behavior previously worked.
Related errors
- Cannot use `" + methodName + "` method without a default typ
- No datasource named '<dataSourceName>' exists
- Unsupported target:
- Invalid type:
- Injected class not found in index:
AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05).
Data as JSON: /api/errors/d5f707914d12e8d2.
Report an issue: GitHub.