{"record":{"id":"5d9c4fef4fb85b95","repo":"apache/beam","slug":"invalid-redis-cursor-s","errorCode":null,"errorMessage":"invalid redis cursor %s","messagePattern":"invalid redis cursor (.+?)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"sdks/java/io/redis/src/main/java/org/apache/beam/sdk/io/redis/RedisCursor.java","lineNumber":126,"sourceCode":"   https://engineering.q42.nl/redis-scan-cursor/\n  */\n  @VisibleForTesting\n  static ByteKey redisCursorToByteKey(RedisCursor cursor) {\n    if (\"0\".equals(cursor.getCursor())) {\n      if (cursor.isStart()) {\n        return ByteKey.of(0x00);\n      } else {\n        return ByteKey.EMPTY;\n      }\n    }\n    int nBits = getTablePow(cursor.getDbSize());\n    long cursorLong = Long.parseLong(cursor.getCursor());\n    long reversed = shiftBits(cursorLong, nBits);\n    ByteArrayOutputStream os = new ByteArrayOutputStream();\n    try {\n      coder.encode(reversed, os);\n    } catch (IOException e) {\n      throw new IllegalArgumentException(\"invalid redis cursor \" + cursor);\n    }\n    byte[] byteArray = os.toByteArray();\n    return ByteKey.copyFrom(byteArray);\n  }\n\n  @VisibleForTesting\n  static long shiftBits(long a, int nBits) {\n    long b = 0;\n    for (int i = 0; i < nBits; ++i) {\n      b <<= 1;\n      b |= (a & 1);\n      a >>= 1;\n    }\n    return b;\n  }\n\n  @VisibleForTesting\n  static int getTablePow(long nKeys) {","sourceCodeStart":108,"sourceCodeEnd":144,"githubUrl":"https://github.com/apache/beam/blob/12126d8942aaf848030c478b4c6a28c6af861c66/sdks/java/io/redis/src/main/java/org/apache/beam/sdk/io/redis/RedisCursor.java#L108-L144","documentation":"RedisCursor.redisCursorToByteKey converts a Redis SCAN cursor into a ByteKey by bit-reversing the cursor and encoding it with a coder. If coder.encode throws an IOException, it is converted to IllegalArgumentException('invalid redis cursor').","triggerScenarios":"Converting a RedisCursor whose cursor string could be parsed but whose encoded long fails coder encoding (coder IO failure during ByteKeyRangeTracker setup).","commonSituations":"Programmatically constructed RedisCursor values not matching Redis's real cursor format; corrupted/edited cursor strings; Beam split bookkeeping handed a bogus cursor.","solutions":["Ensure the cursor originates from an actual Redis SCAN response, not a hand-built value.","Check that the cursor string is a valid non-negative decimal long within unsigned 64-bit range.","Validate cursor round-tripping (byteKeyToRedisCursor) for custom cursor sources.","Catch IllegalArgumentException and fall back to restarting the scan from cursor '0'."],"exampleFix":"// before\nRedisCursor cursor = RedisCursor.of(\"not-a-cursor\"); // or non-SCAN source\n// after\nRedisCursor cursor = RedisCursor.of(\"17\"); // taken directly from a SCAN reply","handlingStrategy":"validation","validationCode":"// Validate cursor before conversion\nString c = cursor.getCursor();\nif (c == null || !c.matches(\"\\\\d+\")) { throw new IllegalArgumentException(\"cursor not from SCAN: \" + c); }\nLong.parseLong(c); // also check unsigned 64-bit range","typeGuard":"boolean isValidRedisCursor(RedisCursor cursor) { try { long v = Long.parseLong(cursor.getCursor()); return v >= 0; } catch (NumberFormatException e) { return false; } }","tryCatchPattern":"try { ByteKey k = RedisCursor.redisCursorToByteKey(cursor); } catch (IllegalArgumentException e) { cursor = RedisCursor.of(\"0\"); /* restart scan */ }","preventionTips":["Only construct RedisCursor values from actual Redis SCAN replies.","Round-trip test with RedisCursor.byteKeyToRedisCursor when persisting cursors.","Clamp stored cursors to '0' on decode failure and rescan."],"tags":["redis","cursor","encoding","apache-beam"],"backgroundTag":"invalid-argument-format","analyzedSha":"12126d8942aaf848030c478b4c6a28c6af861c66","analyzedAt":"2026-09-13T01:50:10.254Z","contentChangedAt":"2026-09-13T01:50:10.254Z","schemaVersion":2},"datasetVersion":"2026-09-14T21:17:11.552Z"}