{"record":{"id":"ca4ff1b5d250de46","repo":"t8y2/dbx","slug":"mongodb-aggregate-option-cursor-must-be-an-object","errorCode":null,"errorMessage":"MongoDB aggregate option cursor must be an object","messagePattern":"MongoDB aggregate option cursor must be an object","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"agents/drivers/mongodb/src/main/java/com/dbx/agent/mongodb/MongoAgent.java","lineNumber":576,"sourceCode":"        }\n        if (!aggregateExplain(options) && !command.containsKey(\"cursor\")) {\n            command.append(\"cursor\", new Document());\n        }\n        return command;\n    }\n\n    private static AggregateIterable<Document> applyAggregateOptions(\n        AggregateIterable<Document> iterable,\n        Document options\n    ) {\n        validateAggregateOptions(options);\n        if (options.containsKey(\"allowDiskUse\")) {\n            iterable = iterable.allowDiskUse(aggregateBoolean(options, \"allowDiskUse\"));\n        }\n        if (options.containsKey(\"cursor\")) {\n            Object rawCursor = options.get(\"cursor\");\n            if (!(rawCursor instanceof Document cursor)) {\n                throw new IllegalArgumentException(\"MongoDB aggregate option cursor must be an object\");\n            }\n            for (String key : cursor.keySet()) {\n                if (!\"batchSize\".equals(key)) {\n                    throw new IllegalArgumentException(\"Unsupported MongoDB aggregate cursor option: \" + key);\n                }\n            }\n            if (cursor.containsKey(\"batchSize\")) {\n                iterable = iterable.batchSize(aggregateNonNegativeInt(cursor, \"batchSize\"));\n            }\n        }\n        if (options.containsKey(\"maxTimeMS\")) {\n            iterable = iterable.maxTime(aggregateNonNegativeLong(options, \"maxTimeMS\"), TimeUnit.MILLISECONDS);\n        }\n        if (options.containsKey(\"maxAwaitTimeMS\")) {\n            iterable = iterable.maxAwaitTime(\n                aggregateNonNegativeLong(options, \"maxAwaitTimeMS\"),\n                TimeUnit.MILLISECONDS\n            );","sourceCodeStart":558,"sourceCodeEnd":594,"githubUrl":"https://github.com/t8y2/dbx/blob/c0390bff16418b651f4728520d99adf8ce48829a/agents/drivers/mongodb/src/main/java/com/dbx/agent/mongodb/MongoAgent.java#L558-L594","documentation":"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.","triggerScenarios":"Calling aggregate with options such as {\"cursor\": true}, {\"cursor\": \"{}\"} (stringified JSON), or {\"cursor\": 100} instead of a nested object.","commonSituations":"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.","solutions":["Pass cursor as an object: new Document(\"batchSize\", 100) or JSON {\"cursor\": {\"batchSize\": 100}}.","If you have a stringified JSON, parse it into a Document/BsonDocument before putting it in options.","If you only wanted a batch size, set it inside the cursor object: {\"cursor\":{\"batchSize\":N}}.","Drop the cursor option if default cursor behavior is acceptable."],"exampleFix":"// before\nDocument options = new Document(\"cursor\", \"{\\\"batchSize\\\": 100}\");\n// after\nDocument options = new Document(\"cursor\", new Document(\"batchSize\", 100));","handlingStrategy":"validation","validationCode":"Object cursor = options.get(\"cursor\");\nif (cursor != null && !(cursor instanceof Document)) {\n    throw new IllegalArgumentException(\"cursor must be an object like {batchSize:N}\");\n}","typeGuard":"static boolean isDocument(Object v) {\n    return v == null || v instanceof Document;\n}","tryCatchPattern":"try {\n    agent.aggregate(db, collection, pipeline, options);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().contains(\"cursor must be an object\")) {\n        Object raw = options.get(\"cursor\");\n        options.put(\"cursor\", Document.parse(String.valueOf(raw)));\n        // retry\n    } else throw e;\n}","preventionTips":["Always pass cursor as {\"batchSize\": N}, never a string or bare number","Parse stringified JSON cursor configs before use","Keep cursor options minimal — only batchSize is supported","Unit-test option payloads built from external config"],"tags":["mongodb","aggregate","type-validation","cursor"],"backgroundTag":"invalid-option-type","analyzedSha":"c0390bff16418b651f4728520d99adf8ce48829a","analyzedAt":"2026-09-05T23:05:10.900Z","contentChangedAt":"2026-09-05T23:05:10.900Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}