redis/jedis · error · IllegalArgumentException
AggregationBuilder must have cursor configured
Error message
AggregationBuilder must have cursor configured
What it means
AggregateIterator iterates the results of a cursor-based (WITHCURSOR) FT.AGGREGATE query, fetching batches via CURSOR. The constructor throws IllegalArgumentException if the supplied AggregationBuilder lacks a cursor, because streaming iteration is impossible without one.
Solutions
- Enable cursoring before iterating: builder.cursor(...) (or withCursor()) on the AggregationBuilder
- Use the non-iterator aggregate() API if you do not need cursor-based iteration
- Check builder.isWithCursor() before constructing AggregateIterator
Example fix
// before
AggregationBuilder ab = new AggregationBuilder("*").groupBy("genre");
AggregateIterator it = new AggregateIterator(client, "idx", ab); // throws
// after
AggregationBuilder ab = new AggregationBuilder("*").groupBy("genre").cursor(100);
AggregateIterator it = new AggregateIterator(client, "idx", ab); Defensive patterns
Strategy: validation
Validate before calling
if (!aggregationBuilder.isWithCursor()) {
aggregationBuilder.cursor(100); // or use aggregate() instead of the iterator
}
AggregateIterator it = new AggregateIterator(provider, indexName, aggregationBuilder); Prevention
- Always call .cursor(batchSize) on aggregation builders used with AggregateIterator
- Prefer the iterator-returning API only when streaming large results; otherwise use aggregate()
- Centralize aggregation construction so cursoring is applied in one place
When it happens
Trigger: Constructing new AggregateIterator(provider, index, aggregationBuilder) with a builder where withCursor()/cursor(...) was never called; calling an iterator-returning API while forgetting to enable cursoring on the aggregation.
Common situations: Building aggregations dynamically and conditionally enabling cursors; copying sample code without the .cursor(...) call; large-result iteration APIs invoked with plain aggregation builders.
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
- cursor must be set
- AGG_SUM policy requires numeric type, but got
- DEFAULT policy requires List, Set, Map, JedisByteHashMap…
AI-assisted analysis of redis/jedis@6dac31d4c2 (2026-09-08).
Data as JSON: /api/errors/172e74b189d7104c.
Report an issue: GitHub.
Appendix: source
Thrown at src/main/java/redis/clients/jedis/search/aggr/AggregateIterator.java:83
private final String indexName;
private final Integer batchSize;
// Connection pool entry - can be either Connection or Pool<Connection>
private final Map.Entry<?, ?> connectionEntry;
private Long cursorId = -1L;
private AggregationResult aggrCommandResult;
/**
* Creates a new AggregateIterator.
* @param connectionProvider the connection provider for cluster/standalone Redis
* @param indexName the search index name
* @param aggregationBuilder the aggregation query with cursor configuration
* @throws IllegalArgumentException if aggregation doesn't have cursor configured
*/
public AggregateIterator(ConnectionProvider connectionProvider, String indexName,
AggregationBuilder aggregationBuilder) {
if (!aggregationBuilder.isWithCursor()) {
throw new IllegalArgumentException("AggregationBuilder must have cursor configured");
}
this.indexName = indexName;
this.batchSize = aggregationBuilder.getCursorCount();
// Get connection pool entry - use getPrimaryNodesConnectionMap() to get pool-based connections
Map<?, ?> connectionMap = connectionProvider.getPrimaryNodesConnectionMap();
if (connectionMap.isEmpty()) {
throw new JedisException("No connections available from connection provider");
}
// Randomly select an entry from the map to distribute load across shards
List<? extends Map.Entry<?, ?>> entries = new ArrayList<>(connectionMap.entrySet());
this.connectionEntry = entries.get(ThreadLocalRandom.current().nextInt(entries.size()));
// Execute initial aggregation command
initializeAggregation(aggregationBuilder);
}
View on GitHub (pinned to 6dac31d4c2)