{"record":{"id":"bb29adfe6775934b","repo":"redisson/redisson","slug":"zscan-cannot-be-called-in-pipeline-transaction-bb29ad","errorCode":null,"errorMessage":"'ZSCAN' cannot be called in pipeline / transaction mode.","messagePattern":"'ZSCAN' cannot be called in pipeline / transaction mode\\.","errorType":"exception","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"redisson-spring/redisson-spring-data/redisson-spring-data-18/src/main/java/org/redisson/spring/data/connection/RedissonConnection.java","lineNumber":1297,"sourceCode":"        if (aggregate != null) {\n            args.add(\"AGGREGATE\");\n            args.add(aggregate.name());\n        }\n        return write(destKey, StringCodec.INSTANCE, ZINTERSTORE, args.toArray());\n    }\n\n    private static final RedisCommand<ListScanResult<Object>> ZSCAN = new RedisCommand<>(\"ZSCAN\", new ListMultiDecoder2(new ListScanResultReplayDecoder(), new ScoredSortedListReplayDecoder()));\n    \n    @Override\n    public Cursor<Tuple> zScan(byte[] key, ScanOptions options) {\n        return new KeyBoundCursor<Tuple>(key, 0, options) {\n\n            private RedisClient client;\n\n            @Override\n            protected ScanIteration<Tuple> doScan(byte[] key, long cursorId, ScanOptions options) {\n                if (isQueueing() || isPipelined()) {\n                    throw new UnsupportedOperationException(\"'ZSCAN' cannot be called in pipeline / transaction mode.\");\n                }\n\n                List<Object> args = new ArrayList<Object>();\n                args.add(key);\n                args.add(Long.toUnsignedString(cursorId));\n                if (options.getPattern() != null) {\n                    args.add(\"MATCH\");\n                    args.add(options.getPattern());\n                }\n                if (options.getCount() != null) {\n                    args.add(\"COUNT\");\n                    args.add(options.getCount());\n                }\n                \n                RFuture<ListScanResult<Tuple>> f = executorService.readAsync(client, key, ByteArrayCodec.INSTANCE, ZSCAN, args.toArray());\n                ListScanResult<Tuple> res = syncFuture(f);\n                client = res.getRedisClient();\n                return new ScanIteration<Tuple>(Long.parseUnsignedLong(res.getPos()), res.getValues());","sourceCodeStart":1279,"sourceCodeEnd":1315,"githubUrl":"https://github.com/redisson/redisson/blob/91188987c2a9023e7fedabff758681ad9b107f25/redisson-spring/redisson-spring-data/redisson-spring-data-18/src/main/java/org/redisson/spring/data/connection/RedissonConnection.java#L1279-L1315","documentation":"Redisson's Spring Data Redis adapter (redisson-spring-data-18) throws UnsupportedOperationException from zScan(byte[], ScanOptions) when ZSCAN cursor iteration is attempted while the connection is queueing (MULTI/transaction) or pipelined. ZSCAN needs interactive round trips per cursor page, which a deferred pipeline/transaction buffer cannot provide.","triggerScenarios":"Calling zScan(...) (directly or via ZSetOperations.scan / RedisTemplate.boundZSetOps(key).scan()) inside executePipelined(...) or inside a multi()/exec() session on the same connection.","commonSituations":"Batch-processing sorted sets inside a wrapped executePipelined block for throughput; porting Jedis-based code that pipelined ZSCAN; leaderboard pagination code moved inside a Redis transaction; version upgrade exposing the previously untested path.","solutions":["Run zScan on a non-pipelined, non-transactional connection (separate RedisTemplate call outside the pipeline block)","Use ZRANGE/ZRANGEBYSCORE with explicit offsets inside pipelines when the set is bounded and small","Scan first into a collection, then pipeline the per-member follow-up operations"],"exampleFix":"// before\nredisTemplate.executePipelined((RedisCallback<Object>) conn -> {\n    conn.zScan(key.getBytes(), ScanOptions.NONE);\n    return null;\n});\n\n// after\ntry (Cursor<Tuple> c = redisTemplate.opsForZSet().scan(key, ScanOptions.NONE)) {\n    List<Object> tuples = new ArrayList<>();\n    c.forEachRemaining(tuples::add);\n}\n// then pipeline follow-up ops on the collected tuples","handlingStrategy":"validation","validationCode":"// Run zScan via the template on a non-pipelined path\ntry (Cursor<Tuple> c = redisTemplate.opsForZSet().scan(zsetKey, ScanOptions.scanOptions().count(500).build())) {\n    c.forEachRemaining(t -> handle(t.getValue()));\n} // ensure no executePipelined wrapper encloses this call","typeGuard":null,"tryCatchPattern":"catch (UnsupportedOperationException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"ZSCAN\")) {\n        // fall back to ZRANGE pagination outside the pipeline\n    } else throw e;\n}","preventionTips":["Keep scan operations in dedicated non-pipelined service methods","Review any executePipelined block for *Scan calls before merging","Use ZRANGE with LIMIT for bounded sets when pipelining is required"],"tags":["redis","redisson","spring-data-redis","pipeline","scan","sorted-set"],"backgroundTag":null,"analyzedSha":"91188987c2a9023e7fedabff758681ad9b107f25","analyzedAt":"2026-08-14T11:39:42.619Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}