t8y2/dbx · error · IllegalArgumentException

MongoDB aggregate option cursor must be an object

Error message

MongoDB aggregate option cursor must be an object

What it means

The 'cursor' aggregate option must be a Document (object) because the library iterates its keys to extract batchSize for the driver's iterable.batchSize(). If the value is a scalar (string, number, boolean) the instanceof Document check fails and IllegalArgumentException is thrown before any query runs.

Source

Thrown at agents/drivers/mongodb/src/main/java/com/dbx/agent/mongodb/MongoAgent.java:576

        }
        if (!aggregateExplain(options) && !command.containsKey("cursor")) {
            command.append("cursor", new Document());
        }
        return command;
    }

    private static AggregateIterable<Document> applyAggregateOptions(
        AggregateIterable<Document> iterable,
        Document options
    ) {
        validateAggregateOptions(options);
        if (options.containsKey("allowDiskUse")) {
            iterable = iterable.allowDiskUse(aggregateBoolean(options, "allowDiskUse"));
        }
        if (options.containsKey("cursor")) {
            Object rawCursor = options.get("cursor");
            if (!(rawCursor instanceof Document cursor)) {
                throw new IllegalArgumentException("MongoDB aggregate option cursor must be an object");
            }
            for (String key : cursor.keySet()) {
                if (!"batchSize".equals(key)) {
                    throw new IllegalArgumentException("Unsupported MongoDB aggregate cursor option: " + key);
                }
            }
            if (cursor.containsKey("batchSize")) {
                iterable = iterable.batchSize(aggregateNonNegativeInt(cursor, "batchSize"));
            }
        }
        if (options.containsKey("maxTimeMS")) {
            iterable = iterable.maxTime(aggregateNonNegativeLong(options, "maxTimeMS"), TimeUnit.MILLISECONDS);
        }
        if (options.containsKey("maxAwaitTimeMS")) {
            iterable = iterable.maxAwaitTime(
                aggregateNonNegativeLong(options, "maxAwaitTimeMS"),
                TimeUnit.MILLISECONDS
            );

View on GitHub (pinned to c0390bff16)

Solutions

  1. Pass cursor as an object: new Document("batchSize", 100) or JSON {"cursor": {"batchSize": 100}}.
  2. If you have a stringified JSON, parse it into a Document/BsonDocument before putting it in options.
  3. If you only wanted a batch size, set it inside the cursor object: {"cursor":{"batchSize":N}}.
  4. Drop the cursor option if default cursor behavior is acceptable.

Example fix

// before
Document options = new Document("cursor", "{\"batchSize\": 100}");
// after
Document options = new Document("cursor", new Document("batchSize", 100));
Defensive patterns

Strategy: validation

Validate before calling

Object cursor = options.get("cursor");
if (cursor != null && !(cursor instanceof Document)) {
    throw new IllegalArgumentException("cursor must be an object like {batchSize:N}");
}

Type guard

static boolean isDocument(Object v) {
    return v == null || v instanceof Document;
}

Try / catch

try {
    agent.aggregate(db, collection, pipeline, options);
} catch (IllegalArgumentException e) {
    if (e.getMessage().contains("cursor must be an object")) {
        Object raw = options.get("cursor");
        options.put("cursor", Document.parse(String.valueOf(raw)));
        // retry
    } else throw e;
}

Prevention

When it happens

Trigger: Calling aggregate with options such as {"cursor": true}, {"cursor": "{}"} (stringified JSON), or {"cursor": 100} instead of a nested object.

Common situations: Passing cursor as a JSON string copied from a mongo shell example without parsing; confusing batchSize (a number inside cursor) with the cursor option itself; older code that sent cursor:{batchSize:N} as an encoded string through an HTTP/MCP payload.

Related errors


AI-assisted analysis of t8y2/dbx@c0390bff16 (2026-09-05). Data as JSON: /api/errors/ca4ff1b5d250de46. Report an issue: GitHub.