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
- Use `scan(cursor, new ScanParams().match("{prefix}.*"))` with a hash-tag enclosed in curly brackets
- Scan each master node individually with dedicated connections if a cluster-wide key listing is required
- 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
- Never use cursor-only SCAN against a cluster client
- Design keys with a common hash-tag prefix so scans can target one slot
- For full keyspace iteration, iterate each master node with its own connection
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
- Not supported in cluster mode.
- Unsupported key type
- Cannot get NodeKey for command with multiple hash slots
- HashImport ' ' expects values but got
- Cache cannot be null!
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)