redisson/redisson · error · UnsupportedOperationException
'SSCAN' cannot be called in pipeline / transaction mode.
Error message
'SSCAN' cannot be called in pipeline / transaction mode.
What it means
Thrown by RedissonConnection.scan(ScanOptions) on a non-cluster setup when the connection is queueing (MULTI) or pipelined. Redisson implements SCAN as a client-driven cursor loop over each master entry (see the MasterSlaveEntry iterator in the anonymous ScanCursor), so each step needs a synchronous response; deferring into a pipeline or transaction would break the iteration protocol.
Source
Thrown at redisson-spring/redisson-spring-data/redisson-spring-data-23/src/main/java/org/redisson/spring/data/connection/RedissonConnection.java:255
CompletableFuture<Void> ff = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
CompletableFuture<Set<byte[]>> future = ff.thenApply(r -> {
return futures.stream().flatMap(f -> f.getNow(new HashSet<>()).stream()).collect(Collectors.toSet());
}).toCompletableFuture();
return sync(new CompletableFutureWrapper<>(future));
}
@Override
public Cursor<byte[]> scan(ScanOptions options) {
return new ScanCursor<byte[]>(0, options) {
private RedisClient client;
private Iterator<MasterSlaveEntry> entries = executorService.getConnectionManager().getEntrySet().iterator();
private MasterSlaveEntry entry = entries.next();
@Override
protected ScanIteration<byte[]> doScan(long cursorId, ScanOptions options) {
if (isQueueing() || isPipelined()) {
throw new UnsupportedOperationException("'SSCAN' cannot be called in pipeline / transaction mode.");
}
if (entry == null) {
return null;
}
List<Object> args = new ArrayList<Object>();
if (cursorId == 101010101010101010L) {
cursorId = 0;
}
args.add(Long.toUnsignedString(cursorId));
if (options.getPattern() != null) {
args.add("MATCH");
args.add(options.getPattern());
}
if (options.getCount() != null) {
args.add("COUNT");
args.add(options.getCount());View on GitHub (pinned to 91188987c2)
Solutions
- Perform the scan before entering pipeline/transaction mode and collect keys into a list
- Do only per-key follow-up commands inside executePipelined
- Use redisson.getKeys().getKeysByPattern() or Redisson RKeys API which manages iteration without Spring Data's connection modes
Example fix
// before
redisTemplate.executePipelined((RedisCallback<Object>) conn -> {
conn.scan(ScanOptions.scanOptions().match("user:*").build())
.forEachRemaining(k -> conn.expire(k, 60));
return null;
});
// after
List<byte[]> keys = new ArrayList<>();
try (Cursor<byte[]> c = redisTemplate.getConnectionFactory().getConnection()
.scan(ScanOptions.scanOptions().match("user:*").build())) {
c.forEachRemaining(keys::add);
}
redisTemplate.executePipelined((RedisCallback<Object>) conn -> {
keys.forEach(k -> conn.expire(k, 60));
return null;
}); Defensive patterns
Strategy: validation
Validate before calling
if (connection.isQueueing() || connection.isPipelined()) {
throw new IllegalStateException("scan requires a plain connection");
}
try (Cursor<byte[]> c = connection.scan(options)) { ... } Try / catch
try { connection.scan(options); } catch (UnsupportedOperationException e) { if (e.getMessage().contains("SSCAN")) { /* rerun on fresh non-pipelined connection */ } else throw e; } Prevention
- Structure jobs as scan-phase (plain) + mutate-phase (pipelined)
- Review every executePipelined block for Cursor usage before merging
- Consider Redisson RKeys.getKeysByPattern() for scan-like iteration
When it happens
Trigger: connection.scan(options) called after multi() or inside executePipelined/executeWithSession on a standard (non-cluster) RedissonConnection.
Common situations: Using RedisTemplate.keys() alternatives inside a pipeline for cache warming or TTL refresh loops; wrapping generic data-access callbacks in SessionCallback that implicitly enables queueing; copying sample code that scans keys inside a transactional callback.
Related errors
- Unable to locate Redisson instance by name: ${jndiName}
- Unable to locate Redisson instance by name: ${jndiName}
- 'SSCAN' cannot be called in pipeline / transaction mode.
- 'SSCAN' cannot be called in pipeline / transaction mode.
- 'ZSCAN' cannot be called in pipeline / transaction mode.
AI-assisted analysis of redisson/redisson@91188987c2 (2026-08-14).
Data as JSON: /api/errors/fb9e1ae54a0bf94b.
Report an issue: GitHub.