{"record":{"id":"7edec897df3e9018","repo":"spring-projects/spring-ai","slug":"unsupported-operand-type-7edec8","errorCode":null,"errorMessage":"Unsupported operand type: ","messagePattern":"Unsupported operand type: ","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"vector-stores/spring-ai-s3-vector-store/src/main/java/org/springframework/ai/vectorstore/s3/S3VectorStoreFilterExpressionEvaluator.java","lineNumber":52,"sourceCode":" * @author Jewoo Shin\n */\nfinal class S3VectorStoreFilterExpressionEvaluator {\n\n\tprivate static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern(\"yyyy-MM-dd'T'HH:mm:ss'Z'\")\n\t\t.withZone(ZoneOffset.UTC);\n\n\tboolean evaluate(Filter.Expression expression, Map<String, Object> metadata) {\n\t\treturn evaluateExpression(expression, metadata);\n\t}\n\n\tprivate boolean evaluateOperand(Filter.Operand operand, Map<String, Object> metadata) {\n\t\tif (operand instanceof Filter.Group group) {\n\t\t\treturn evaluateOperand(group.content(), metadata);\n\t\t}\n\t\tif (operand instanceof Filter.Expression expression) {\n\t\t\treturn evaluateExpression(expression, metadata);\n\t\t}\n\t\tthrow new IllegalArgumentException(\"Unsupported operand type: \" + operand.getClass().getName());\n\t}\n\n\tprivate boolean evaluateExpression(Filter.Expression expression, Map<String, Object> metadata) {\n\t\treturn switch (expression.type()) {\n\t\t\tcase AND -> evaluateOperand(left(expression), metadata) && evaluateOperand(right(expression), metadata);\n\t\t\tcase OR -> evaluateOperand(left(expression), metadata) || evaluateOperand(right(expression), metadata);\n\t\t\tcase NOT -> !evaluateOperand(left(expression), metadata);\n\t\t\tcase EQ -> compare(metadataValue(left(expression), metadata), filterValue(right(expression))) == 0;\n\t\t\tcase NE -> compare(metadataValue(left(expression), metadata), filterValue(right(expression))) != 0;\n\t\t\tcase GT -> compare(metadataValue(left(expression), metadata), filterValue(right(expression))) > 0;\n\t\t\tcase GTE -> compare(metadataValue(left(expression), metadata), filterValue(right(expression))) >= 0;\n\t\t\tcase LT -> compare(metadataValue(left(expression), metadata), filterValue(right(expression))) < 0;\n\t\t\tcase LTE -> compare(metadataValue(left(expression), metadata), filterValue(right(expression))) <= 0;\n\t\t\tcase IN -> {\n\t\t\t\tObject metaVal = metadataValue(left(expression), metadata);\n\t\t\t\tList<?> list = asList(filterValue(right(expression)), expression);\n\t\t\t\tyield list.stream().anyMatch(item -> compare(metaVal, item) == 0);\n\t\t\t}","sourceCodeStart":34,"sourceCodeEnd":70,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/vector-stores/spring-ai-s3-vector-store/src/main/java/org/springframework/ai/vectorstore/s3/S3VectorStoreFilterExpressionEvaluator.java#L34-L70","documentation":"S3VectorStoreFilterExpressionEvaluator.evaluateOperand recursively evaluates Filter operands against row metadata and only understands Filter.Group and Filter.Expression. A bare Filter.Key or Filter.Value operand at an expression position is a malformed filter tree, so it throws IllegalArgumentException naming the operand class.","triggerScenarios":"Hand-building a Filter tree where an AND/OR child operand is a plain Key or Value instead of an Expression/Group, then running S3VectorStore similaritySearch that triggers post-filter evaluation of ListVectors metadata.","commonSituations":"Programmatic filter construction mistakes (missing comparison around a key); serialized/persisted filters from another store that don't round-trip to valid expressions; reflection-based filter builders.","solutions":["Ensure every AND/OR/NOT child is a complete comparison expression (key OP value) or a Group, never a bare operand.","Build filters with the Filter expression builder API (Filter.expr / Filter.builder) instead of manual constructor calls so the tree is validated by construction.","Add a pre-check that walks the filter tree and asserts operands at logical-operator positions are Expression/Group.","If you control the code path, catch IllegalArgumentException around evaluate and fail fast with a clearer message."],"exampleFix":"// before\nnew Filter.Expression(Filter.ExpressionType.AND, new Filter.Key(\"a\"), eq(\"b\", 1))\n// after\nnew Filter.Expression(Filter.ExpressionType.AND, eq(\"a\", 1), eq(\"b\", 1))","handlingStrategy":"validation","validationCode":"static void validateOperands(Filter.Expression e) {\n  for (Filter.Operand o : new Filter.Operand[]{ e.left(), e.right() }) {\n    if (o == null) continue;\n    if (o instanceof Filter.Expression x) validateOperands(x);\n    else if (!(o instanceof Filter.Group) && e.type() == AND || e.type() == OR)\n      throw new IllegalArgumentException(\"Bare operand under logical operator: \" + o.getClass());\n  }\n}","typeGuard":"static boolean isLogicalOperand(Filter.Operand o) { return o instanceof Filter.Expression || o instanceof Filter.Group; }","tryCatchPattern":"try { store.similaritySearch(req); } catch (IllegalArgumentException e) { if (e.getMessage().contains(\"Unsupported operand type\")) { /* rebuild filter tree */ } }","preventionTips":["Build filters with Filter.expr(...) or the builder API instead of manual constructors.","Every AND/OR child must be a full comparison or Group.","Unit-test programmatic filter assembly with a tree validator."],"tags":["vector-store","filter-expression","malformed-filter","post-filtering","aws-s3-vectors"],"backgroundTag":"invalid-argument-value","analyzedSha":"98a7beda4f29d80a71c5837eb4053b03a93a46f7","analyzedAt":"2026-09-11T14:15:49.441Z","contentChangedAt":"2026-09-11T14:15:49.441Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}