apache/beam · error · IllegalArgumentException

invalid redis cursor %s

Error message

invalid redis cursor %s

What it means

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').

Source

Thrown at sdks/java/io/redis/src/main/java/org/apache/beam/sdk/io/redis/RedisCursor.java:126

   https://engineering.q42.nl/redis-scan-cursor/
  */
  @VisibleForTesting
  static ByteKey redisCursorToByteKey(RedisCursor cursor) {
    if ("0".equals(cursor.getCursor())) {
      if (cursor.isStart()) {
        return ByteKey.of(0x00);
      } else {
        return ByteKey.EMPTY;
      }
    }
    int nBits = getTablePow(cursor.getDbSize());
    long cursorLong = Long.parseLong(cursor.getCursor());
    long reversed = shiftBits(cursorLong, nBits);
    ByteArrayOutputStream os = new ByteArrayOutputStream();
    try {
      coder.encode(reversed, os);
    } catch (IOException e) {
      throw new IllegalArgumentException("invalid redis cursor " + cursor);
    }
    byte[] byteArray = os.toByteArray();
    return ByteKey.copyFrom(byteArray);
  }

  @VisibleForTesting
  static long shiftBits(long a, int nBits) {
    long b = 0;
    for (int i = 0; i < nBits; ++i) {
      b <<= 1;
      b |= (a & 1);
      a >>= 1;
    }
    return b;
  }

  @VisibleForTesting
  static int getTablePow(long nKeys) {

View on GitHub (pinned to 12126d8942)

Solutions

  1. Ensure the cursor originates from an actual Redis SCAN response, not a hand-built value.
  2. Check that the cursor string is a valid non-negative decimal long within unsigned 64-bit range.
  3. Validate cursor round-tripping (byteKeyToRedisCursor) for custom cursor sources.
  4. Catch IllegalArgumentException and fall back to restarting the scan from cursor '0'.

Example fix

// before
RedisCursor cursor = RedisCursor.of("not-a-cursor"); // or non-SCAN source
// after
RedisCursor cursor = RedisCursor.of("17"); // taken directly from a SCAN reply
Defensive patterns

Strategy: validation

Validate before calling

// Validate cursor before conversion
String c = cursor.getCursor();
if (c == null || !c.matches("\\d+")) { throw new IllegalArgumentException("cursor not from SCAN: " + c); }
Long.parseLong(c); // also check unsigned 64-bit range

Type guard

boolean isValidRedisCursor(RedisCursor cursor) { try { long v = Long.parseLong(cursor.getCursor()); return v >= 0; } catch (NumberFormatException e) { return false; } }

Try / catch

try { ByteKey k = RedisCursor.redisCursorToByteKey(cursor); } catch (IllegalArgumentException e) { cursor = RedisCursor.of("0"); /* restart scan */ }

Prevention

When it happens

Trigger: Converting a RedisCursor whose cursor string could be parsed but whose encoded long fails coder encoding (coder IO failure during ByteKeyRangeTracker setup).

Common situations: Programmatically constructed RedisCursor values not matching Redis's real cursor format; corrupted/edited cursor strings; Beam split bookkeeping handed a bogus cursor.

Understand the failure class

Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.

Related errors


AI-assisted analysis of apache/beam@12126d8942 (2026-09-13). Data as JSON: /api/errors/5d9c4fef4fb85b95. Report an issue: GitHub.