{"record":{"id":"d5f707914d12e8d2","repo":"quarkusio/quarkus","slug":"cannot-cache-null-value","errorCode":null,"errorMessage":"Cannot cache `null` value","messagePattern":"Cannot cache `null` value","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"extensions/redis-cache/runtime/src/main/java/io/quarkus/cache/redis/runtime/RedisCacheImpl.java","lineNumber":193,"sourceCode":"                return startingPoint\n                        .chain(Unchecked.function(new UncheckedFunction<>() {\n                            @Override\n                            public Uni<V> apply(V cached) throws Exception {\n                                if (cached != null) {\n                                    // Unwatch if optimistic locking\n                                    if (cacheInfo.useOptimisticLocking) {\n                                        return connection.send(Request.cmd(Command.UNWATCH))\n                                                .replaceWith(cached);\n                                    }\n                                    return Uni.createFrom().item(new StaticSupplier<>(cached));\n                                } else {\n                                    Uni<V> uni = computeValue(key, valueLoader, isWorkerThread);\n\n                                    return uni.onItem().call(new Function<V, Uni<?>>() {\n                                        @Override\n                                        public Uni<?> apply(V value) {\n                                            if (value == null) {\n                                                throw new IllegalArgumentException(\"Cannot cache `null` value\");\n                                            }\n                                            byte[] encodedValue = marshaller.encode(value);\n                                            Uni<V> result;\n                                            if (cacheInfo.useOptimisticLocking) {\n                                                result = multi(connection, set(connection, encodedKey, encodedValue))\n                                                        .replaceWith(value);\n                                            } else {\n                                                result = set(connection, encodedKey, encodedValue).replaceWith(value);\n                                            }\n                                            if (isWorkerThread) {\n                                                return result.runSubscriptionOn(\n                                                        MutinyHelper.blockingExecutor(vertx.getDelegate(), false));\n                                            }\n                                            return result;\n                                        }\n                                    });\n                                }\n                            }","sourceCodeStart":175,"sourceCodeEnd":211,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/extensions/redis-cache/runtime/src/main/java/io/quarkus/cache/redis/runtime/RedisCacheImpl.java#L175-L211","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nV v = cache.get(key, k -> maybeNullLoader(k));\n// after\nOptional<V> opt = cache.get(key, k -> Optional.ofNullable(maybeNullLoader(k)));\nV v = opt.orElse(null);","handlingStrategy":"try-catch","validationCode":"V value = loader.apply(key);\nif (value == null) throw new IllegalStateException(\"Loader must not return null for redis cache\");","typeGuard":null,"tryCatchPattern":"try {\n    V v = cache.get(key, loader);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().contains(\"Cannot cache `null` value\")) {\n        // treat as cache miss / not-found and fall back\n    } else throw e;\n}","preventionTips":["Wrap nullable results in Optional before caching","Use sentinel EMPTY values for redis caches","Document that redis caches are null-intolerant"],"tags":["quarkus","redis-cache","null","cache","runtime"],"backgroundTag":"null-value-not-allowed","analyzedSha":"e1c734241f34c7919086ceb4c9262b4a58f6de44","analyzedAt":"2026-09-05T17:01:29.979Z","contentChangedAt":"2026-09-05T17:01:29.979Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}