redisson/redisson · error · IllegalArgumentException

Command must not define expiration nor option for GETSET.

Error message

Command must not define expiration nor option for GETSET.

What it means

RedissonReactiveStringCommands.getSet(Publisher<SetCommand>) throws IllegalArgumentException('Command must not define expiration nor option for GETSET.') when a SetCommand passed to GETSET carries an expiration or a set option. GETSET is semantically a plain atomic replace-and-return-old-value command and maps to no SET EX/PX/NX/XX variant, so those fields must be empty.

Source

Thrown at redisson-spring/redisson-spring-data/redisson-spring-data-20/src/main/java/org/redisson/spring/data/connection/RedissonReactiveStringCommands.java:122

            Assert.notNull(command.getKey(), "Key must not be null!");

            byte[] keyBuf = toByteArray(command.getKey());
            Mono<byte[]> m = read(keyBuf, ByteArrayCodec.INSTANCE, RedisCommands.GET, keyBuf);
            return m.map(v -> new ByteBufferResponse<>(command, ByteBuffer.wrap(v)))
                    .defaultIfEmpty(new AbsentByteBufferResponse<>(command));
        });
    }

    @Override
    public Flux<ByteBufferResponse<SetCommand>> getSet(Publisher<SetCommand> commands) {
        return execute(commands, command -> {

            Assert.notNull(command.getKey(), "Key must not be null!");
            Assert.notNull(command.getValue(), "Value must not be null!");

            if (command.getExpiration().isPresent() || command.getOption().isPresent()) {
                throw new IllegalArgumentException("Command must not define expiration nor option for GETSET.");
            }

            byte[] keyBuf = toByteArray(command.getKey());
            byte[] valueBuf = toByteArray(command.getValue());
            
            Mono<byte[]> m = write(keyBuf, ByteArrayCodec.INSTANCE, RedisCommands.GETSET, keyBuf, valueBuf);
            return m.map(v -> new ByteBufferResponse<>(command, ByteBuffer.wrap(v)));
        });
    }

    @Override
    public Flux<MultiValueResponse<List<ByteBuffer>, ByteBuffer>> mGet(Publisher<List<ByteBuffer>> keysets) {
        return execute(keysets, coll -> {

            Assert.notNull(coll, "List must not be null!");
            
            Object[] params = coll.stream().map(buf -> toByteArray(buf)).toArray(Object[]::new);

View on GitHub (pinned to 91188987c2)

Solutions

  1. Use SetCommand.set(key, value) with no expiration and no option when calling getSet.
  2. If TTL/conditional semantics are needed, use set(...) and a separate get(...), or the SET command's own options via set().
  3. Add a debug assertion that getExpiration() and getOption() are empty before invoking getSet.

Example fix

// before
SetCommand cmd = SetCommand.set(key, value, Expiration.from(60, TimeUnit.SECONDS));
stringCommands.getSet(Flux.just(cmd)); // throws

// after
SetCommand cmd = SetCommand.set(key, value);
stringCommands.getSet(Flux.just(cmd));
Defensive patterns

Strategy: validation

Validate before calling

if (cmd.getExpiration().isPresent() || cmd.getOption().isPresent()) {
    throw new IllegalArgumentException("GETSET requires a bare SetCommand");
}
stringCommands.getSet(Flux.just(cmd));

Type guard

boolean isBareSetCommand(SetCommand cmd) {
    return !cmd.getExpiration().isPresent() && !cmd.getOption().isPresent();
}

Prevention

When it happens

Trigger: Building a SetCommand with SetOption.expiration(Expiration) or SetOption.ifExists()/ifAbsent() (or command.set(…)) and then submitting it to getSet(...) instead of set(...).

Common situations: Copy-pasting SetCommand construction from a set() call site into getSet(); generic 'write value with TTL' helpers routed to the wrong command method.

Related errors


AI-assisted analysis of redisson/redisson@91188987c2 (2026-08-14). Data as JSON: /api/errors/e10611238aec1075. Report an issue: GitHub.