{"record":{"id":"1af4b60982b1ab93","repo":"redisson/redisson","slug":"expiration-must-not-be-null-1af4b6","errorCode":null,"errorMessage":"Expiration must not be null!","messagePattern":"Expiration must not be null!","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"redisson-spring/redisson-spring-data/redisson-spring-data-25/src/main/java/org/redisson/spring/data/connection/RedissonReactiveStringCommands.java","lineNumber":173,"sourceCode":"            byte[] keyBuf = toByteArray(command.getKey());\n            byte[] valueBuf = toByteArray(command.getValue());\n            \n            Mono<Boolean> m = write(keyBuf, StringCodec.INSTANCE, RedisCommands.SETNX, keyBuf, valueBuf);\n            return m.map(v -> new BooleanResponse<>(command, v));\n        });\n    }\n\n    private static final RedisCommand<Boolean> SETEX = new RedisCommand<Boolean>(\"SETEX\", new BooleanReplayConvertor());\n    \n    @Override\n    public Flux<BooleanResponse<SetCommand>> setEX(Publisher<SetCommand> commands) {\n        return execute(commands, command -> {\n\n            Assert.notNull(command.getKey(), \"Key must not be null!\");\n            Assert.notNull(command.getValue(), \"Value must not be null!\");\n\n            if (!command.getExpiration().isPresent()) {\n                throw new IllegalArgumentException(\"Expiration must not be null!\");\n            }\n\n            byte[] keyBuf = toByteArray(command.getKey());\n            byte[] valueBuf = toByteArray(command.getValue());\n            \n            Mono<Boolean> m = write(keyBuf, StringCodec.INSTANCE, SETEX, \n                    keyBuf, command.getExpiration().get().getExpirationTimeInSeconds(), valueBuf);\n            return m.map(v -> new BooleanResponse<>(command, v));\n        });\n    }\n\n    private static final RedisCommand<String> PSETEX = new RedisCommand<String>(\"PSETEX\");\n    \n    @Override\n    public Flux<BooleanResponse<SetCommand>> pSetEX(Publisher<SetCommand> commands) {\n        return execute(commands, command -> {\n\n            Assert.notNull(command.getKey(), \"Key must not be null!\");","sourceCodeStart":155,"sourceCodeEnd":191,"githubUrl":"https://github.com/redisson/redisson/blob/91188987c2a9023e7fedabff758681ad9b107f25/redisson-spring/redisson-spring-data/redisson-spring-data-25/src/main/java/org/redisson/spring/data/connection/RedissonReactiveStringCommands.java#L155-L191","documentation":"SETEX requires exactly three arguments: key, seconds, value. RedissonReactiveStringCommands.setEX maps Spring Data SetCommand to SETEX and validates that an expiration is present; without it the command cannot be serialized, so it throws IllegalArgumentException('Expiration must not be null!') before any I/O happens. The check mirrors SETEX's signature — the TTL is not optional.","triggerScenarios":"Calling reactiveStringCommands.setEX(...) with SetCommand.set(key, value) that never had .ex(Duration) applied; or building the SetCommand with a null Expiration. Reached via ReactiveStringCommands or reactive RedisTemplate helpers that dispatch on the presence of a TTL.","commonSituations":"Conditional TTL logic (e.g. 'persist if flag unset') where the code path forgets to attach .ex(); refactoring where an expiration field becomes optional; copy-paste from the plain set() path where expiration is legal to omit.","solutions":["Always attach a TTL: SetCommand.set(key, value).ex(Duration.ofSeconds(60)) before calling setEX","If the TTL is genuinely optional, branch: use setEX when a TTL exists and the plain set() overload when it does not","Note SETEX is deprecated in Redis >= 2.6.12; prefer set() with .ex(...) for new code"],"exampleFix":"// before\nSetCommand cmd = SetCommand.set(key, value); // no expiration\nreactiveStringCommands.setEX(Flux.just(cmd)); // throws\n\n// after\nSetCommand cmd = SetCommand.set(key, value)\n    .ex(Duration.ofSeconds(60));\nreactiveStringCommands.setEX(Flux.just(cmd));","handlingStrategy":"validation","validationCode":"// Ensure expiration before setEX:\nSetCommand cmd = SetCommand.set(key, value);\nDuration ttl = optionalTtl != null ? optionalTtl : Duration.ofSeconds(60);\ncmd = cmd.ex(ttl);\nreactiveStringCommands.setEX(Flux.just(cmd));","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always call .ex(Duration) on SetCommand before setEX/pSetEX","Branch on TTL presence: setEX when present, plain set() when not","Prefer the modern set() overload with expiration — SETEX is legacy"],"tags":["redis","redisson","spring-data-redis","setex","validation","illegal-argument"],"backgroundTag":null,"analyzedSha":"91188987c2a9023e7fedabff758681ad9b107f25","analyzedAt":"2026-08-14T11:39:42.619Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}