{"record":{"id":"04b7a7298a2e4e1d","repo":"redis/jedis","slug":"cursor-must-be-set","errorCode":null,"errorMessage":"cursor must be set","messagePattern":"cursor must be set","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"src/main/java/redis/clients/jedis/search/aggr/FtAggregateIteration.java","lineNumber":29,"sourceCode":" * @deprecated Since Redis 8.0, FT.AGGREGATE automatically retrieves results from all cluster nodes,\n *             eliminating the need for manual iteration across nodes. Use {@link AggregateIterator}\n *             instead, which provides better cursor management and connection handling.\n */\n@Deprecated\npublic class FtAggregateIteration extends JedisCommandIterationBase<AggregationResult, Row> {\n\n  private final String indexName;\n  private final CommandArguments args;\n\n  /**\n   * {@link AggregationBuilder#cursor(int, long) CURSOR} must be set.\n   * @param connectionProvider connection provider\n   * @param indexName index name\n   * @param aggr cursor must be set\n   */\n  public FtAggregateIteration(ConnectionProvider connectionProvider, String indexName, AggregationBuilder aggr) {\n    super(connectionProvider, AggregationResult.SEARCH_AGGREGATION_RESULT_WITH_CURSOR);\n    if (!aggr.isWithCursor()) throw new IllegalArgumentException(\"cursor must be set\");\n    this.indexName = indexName;\n    this.args = new CommandArguments(SearchProtocol.SearchCommand.AGGREGATE).add(this.indexName).addParams(aggr);\n  }\n\n  @Override\n  protected boolean isNodeCompleted(AggregationResult reply) {\n    return reply.getCursorId() == 0L;\n  }\n\n  @Override\n  protected CommandArguments initCommandArguments() {\n    return args;\n  }\n\n  @Override\n  protected CommandArguments nextCommandArguments(AggregationResult lastReply) {\n    return new CommandArguments(SearchProtocol.SearchCommand.CURSOR).add(SearchProtocol.SearchKeyword.READ)\n        .add(indexName).add(lastReply.getCursorId());","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/redis/jedis/blob/6dac31d4c224fb3257c216f3985340c6f500cdcb/src/main/java/redis/clients/jedis/search/aggr/FtAggregateIteration.java#L11-L47","documentation":"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.","triggerScenarios":"new FtAggregateIteration(provider, \"idx\", new AggregationBuilder().filter(...)) — i.e. an aggregation built without .cursor(...) / WITHCURSOR set.","commonSituations":"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.","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"],"exampleFix":"// before\nAggregationBuilder aggr = new AggregationBuilder().groupBy(\"@type\");\nFtAggregateIteration it = new FtAggregateIteration(provider, \"idx\", aggr); // IllegalArgumentException\n// after\nAggregationBuilder aggr = new AggregationBuilder().groupBy(\"@type\").cursor(100);\nFtAggregateIteration it = new FtAggregateIteration(provider, \"idx\", aggr);","handlingStrategy":"validation","validationCode":"if (!aggr.isWithCursor()) {\n  throw new IllegalArgumentException(\"FtAggregateIteration requires an AggregationBuilder with cursor enabled\");\n}","typeGuard":"boolean supportsIteration(AggregationBuilder aggr) {\n  return aggr != null && aggr.isWithCursor();\n}","tryCatchPattern":"try {\n  FtAggregateIteration it = new FtAggregateIteration(provider, indexName, aggr);\n} catch (IllegalArgumentException e) {\n  // builder lacks WITHCURSOR — enable cursor or use non-iterating API\n}","preventionTips":["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"],"tags":["jedis","search","aggregation","cursor","argument-validation"],"backgroundTag":"missing-required-argument","analyzedSha":"6dac31d4c224fb3257c216f3985340c6f500cdcb","analyzedAt":"2026-09-08T04:55:01.204Z","contentChangedAt":"2026-09-08T04:55:01.204Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}