redis/jedis · error · IllegalArgumentException

Cluster mode only supports SCAN command with MATCH pattern…

Error message

Cluster mode only supports SCAN command with MATCH pattern containing hash-tag ( curly-brackets enclosed string )

What it means

In cluster mode, SCAN without a MATCH pattern cannot be routed to a hash slot, so ClusterCommandObjects.scan(String cursor) unconditionally throws IllegalArgumentException. Redis Cluster requires key-space operations to target a slot, and a MATCH pattern containing a hash-tag ({...}) provides that slot.

Solutions

  1. Use `scan(cursor, new ScanParams().match("{prefix}.*"))` with a hash-tag enclosed in curly brackets
  2. Scan each master node individually with dedicated connections if a cluster-wide key listing is required
  3. Use a non-cluster client for single-node setups where untagged SCAN is valid

Example fix

// before
ScanResult<String> r = cluster.scan(cursor);
// after
ScanResult<String> r = cluster.scan(cursor, new ScanParams().match("{user:1}:*"));
Defensive patterns

Strategy: validation

Validate before calling

if (JedisClusterHashTag.isClusterCompliantMatchPattern(pattern)) { result = cluster.scan(cursor, new ScanParams().match(pattern)); } else { /* scan per node or fix pattern */ }

Type guard

boolean isClusterSafePattern(String p) { return p != null && p.contains("{") && p.contains("}"); }

Try / catch

try { cluster.scan(cursor); } catch (IllegalArgumentException e) { throw new IllegalStateException("Cluster SCAN requires a hash-tagged MATCH pattern; use scan(cursor, params)", e); }

Prevention

When it happens

Trigger: Calling `clusterClient.scan(cursor)` (or JedisCluster scan variants) on a RedisClusterClient/ClusterCommandObjects with only a cursor and no MATCH-containing-hash-tag params.

Common situations: Porting standalone-mode scanning code to Redis Cluster; iterating all keys in a cluster the same way as a single-node Redis.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of redis/jedis@6dac31d4c2 (2026-09-08). Data as JSON: /api/errors/e6eda177c4bf6c65. Report an issue: GitHub.

Appendix: source

Thrown at src/main/java/redis/clients/jedis/ClusterCommandObjects.java:34

import java.util.stream.Collectors;

import static redis.clients.jedis.Protocol.Command.*;
import static redis.clients.jedis.Protocol.Keyword.TYPE;

public class ClusterCommandObjects extends CommandObjects {

  private static final String CLUSTER_UNSUPPORTED_MESSAGE = "Not supported in cluster mode.";

  public ClusterCommandObjects(RedisProtocol protocol) {
    super(protocol);
  }

  private static final String SCAN_PATTERN_MESSAGE = "Cluster mode only supports SCAN command"
      + " with MATCH pattern containing hash-tag ( curly-brackets enclosed string )";

  @Override
  public final CommandObject<ScanResult<String>> scan(String cursor) {
    throw new IllegalArgumentException(SCAN_PATTERN_MESSAGE);
  }

  @Override
  public final CommandObject<ScanResult<String>> scan(String cursor, ScanParams params) {
    String match = params.match();
    if (match == null || !JedisClusterHashTag.isClusterCompliantMatchPattern(match)) {
      throw new IllegalArgumentException(SCAN_PATTERN_MESSAGE);
    }
    return new CommandObject<>(commandArguments(SCAN).add(cursor).addParams(params).addHashSlotKey(match), BuilderFactory.SCAN_RESPONSE);
  }

  @Override
  public final CommandObject<ScanResult<String>> scan(String cursor, ScanParams params, String type) {
    String match = params.match();
    if (match == null || !JedisClusterHashTag.isClusterCompliantMatchPattern(match)) {
      throw new IllegalArgumentException(SCAN_PATTERN_MESSAGE);
    }
    return new CommandObject<>(commandArguments(SCAN).add(cursor).addParams(params).addHashSlotKey(match).add(TYPE).add(type), BuilderFactory.SCAN_RESPONSE);

View on GitHub (pinned to 6dac31d4c2)