redis/jedis · error · IllegalArgumentException
cursor must be set
Error message
cursor must be set
What it means
FtAggregateIteration is the cursor-driven aggregation iterator wrapper; it only works with aggregations created with WITHCURSOR. Passing an AggregationBuilder without isWithCursor() means there would be no cursor to iterate, so the constructor throws this IllegalArgumentException immediately.
Solutions
- Add .cursor(...) / enable withCursor on the AggregationBuilder before passing it to FtAggregateIteration
- If you don't need cursor-based iteration, use the non-cursor aggregation API instead of FtAggregateIteration
- Assert aggr.isWithCursor() in helper code to fail early with a clearer message
Example fix
// before
AggregationBuilder aggr = new AggregationBuilder().groupBy("@type");
FtAggregateIteration it = new FtAggregateIteration(provider, "idx", aggr); // IllegalArgumentException
// after
AggregationBuilder aggr = new AggregationBuilder().groupBy("@type").cursor(100);
FtAggregateIteration it = new FtAggregateIteration(provider, "idx", aggr); Defensive patterns
Strategy: validation
Validate before calling
if (!aggr.isWithCursor()) {
throw new IllegalArgumentException("FtAggregateIteration requires an AggregationBuilder with cursor enabled");
} Type guard
boolean supportsIteration(AggregationBuilder aggr) {
return aggr != null && aggr.isWithCursor();
} Try / catch
try {
FtAggregateIteration it = new FtAggregateIteration(provider, indexName, aggr);
} catch (IllegalArgumentException e) {
// builder lacks WITHCURSOR — enable cursor or use non-iterating API
} Prevention
- Always call .cursor(...) on the AggregationBuilder when using FtAggregateIteration
- Keep cursor-mode builders and one-shot builders in separate code paths/helpers
- Assert isWithCursor() in wrapper APIs before delegating to the iteration class
When it happens
Trigger: new FtAggregateIteration(provider, "idx", new AggregationBuilder().filter(...)) — i.e. an aggregation built without .cursor(...) / WITHCURSOR set.
Common situations: Switching from a one-shot AggregateIterator to FtAggregateIteration (or vice versa) without enabling cursor mode; reusing a builder intended for synchronous aggregate() calls; forgetting the withCursor() fluent call in the chain.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
- Failed to fetch next aggregation batch
- Failed to initialize aggregation cursor
- LIMIT offset and count must be non-negative
- DIALECT=0 cannot be set.
- No connections available from connection provider
AI-assisted analysis of redis/jedis@6dac31d4c2 (2026-09-08).
Data as JSON: /api/errors/04b7a7298a2e4e1d.
Report an issue: GitHub.
Appendix: source
Thrown at src/main/java/redis/clients/jedis/search/aggr/FtAggregateIteration.java:29
* @deprecated Since Redis 8.0, FT.AGGREGATE automatically retrieves results from all cluster nodes,
* eliminating the need for manual iteration across nodes. Use {@link AggregateIterator}
* instead, which provides better cursor management and connection handling.
*/
@Deprecated
public class FtAggregateIteration extends JedisCommandIterationBase<AggregationResult, Row> {
private final String indexName;
private final CommandArguments args;
/**
* {@link AggregationBuilder#cursor(int, long) CURSOR} must be set.
* @param connectionProvider connection provider
* @param indexName index name
* @param aggr cursor must be set
*/
public FtAggregateIteration(ConnectionProvider connectionProvider, String indexName, AggregationBuilder aggr) {
super(connectionProvider, AggregationResult.SEARCH_AGGREGATION_RESULT_WITH_CURSOR);
if (!aggr.isWithCursor()) throw new IllegalArgumentException("cursor must be set");
this.indexName = indexName;
this.args = new CommandArguments(SearchProtocol.SearchCommand.AGGREGATE).add(this.indexName).addParams(aggr);
}
@Override
protected boolean isNodeCompleted(AggregationResult reply) {
return reply.getCursorId() == 0L;
}
@Override
protected CommandArguments initCommandArguments() {
return args;
}
@Override
protected CommandArguments nextCommandArguments(AggregationResult lastReply) {
return new CommandArguments(SearchProtocol.SearchCommand.CURSOR).add(SearchProtocol.SearchKeyword.READ)
.add(indexName).add(lastReply.getCursorId());View on GitHub (pinned to 6dac31d4c2)