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

  1. Add .cursor(...) / enable withCursor on the AggregationBuilder before passing it to FtAggregateIteration
  2. If you don't need cursor-based iteration, use the non-cursor aggregation API instead of FtAggregateIteration
  3. 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

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


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)