{"record":{"id":"cd0b405ccd71f917","repo":"spring-projects/spring-ai","slug":"unsupported-operand-type","errorCode":null,"errorMessage":"Unsupported operand type: ","messagePattern":"Unsupported operand type: ","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"spring-ai-vector-store/src/main/java/org/springframework/ai/vectorstore/SimpleVectorStoreFilterExpressionEvaluator.java","lineNumber":91,"sourceCode":"\t * @param metadata the document metadata to match against; must not be {@code null}\n\t * @return {@code true} if the metadata satisfies the expression\n\t */\n\tpublic boolean 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\t// Filter.Key and Filter.Value are leaf operands consumed directly by\n\t\t// metadataValue() and filterValue() inside evaluateExpression(). They are never\n\t\t// passed here as top-level boolean operands, so this branch is unreachable under\n\t\t// normal usage.\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\t// Unary operator: only the left operand is used. Ignore right operand\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);","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/spring-ai-vector-store/src/main/java/org/springframework/ai/vectorstore/SimpleVectorStoreFilterExpressionEvaluator.java#L73-L109","documentation":"SimpleVectorStoreFilterExpressionEvaluator.evaluateOperand throws IllegalArgumentException 'Unsupported operand type: <class name>' when a Filter.Operand passed to it is not a Filter.Expression, Filter.Key, or Filter.Value. Per the source, Key/Value leaves are consumed inside evaluateExpression, so reaching this throw means the expression tree contains an operand type the evaluator does not recognize.","triggerScenarios":"Building a Filter expression tree programmatically with a custom or unanticipated Operand subclass; calling evaluateOperand directly with a Filter.Key or Filter.Value; a newer Vector Search DSL introducing operand types the evaluator does not handle.","commonSituations":"Custom filter construction instead of using Filter expression builders (eq, gt, and, or...); version drift between the search DSL and the store's evaluator.","solutions":["Inspect the logged class name and build filters only via supported DSL operators (and/or/not/eq/gt/lt/in etc.).","Do not pass Filter.Key/Filter.Value to evaluateOperand directly; wrap them in a comparison Expression.","Upgrade spring-ai so the evaluator recognizes any newly introduced operand types."],"exampleFix":"// before\nevaluator.evaluateOperand(new Filter.Key(\"genre\"), metadata); // unsupported here\n\n// after\nFilter.Expression expr = new Filter.Expression(ExpressionType.EQ,\n    new Filter.Key(\"genre\"), new Filter.Value(\"sci-fi\"));\nevaluator.evaluate(expr, metadata);","handlingStrategy":"validation","validationCode":"// build filters only with supported DSL operators\nSearchRequest request = SearchRequest.query(q)\n    .withFilterExpression(\"genre == 'sci-fi' and year > 2020\");","typeGuard":"static boolean isSupportedOperand(Filter.Operand o) {\n    return o instanceof Filter.Expression; // Key/Value must be wrapped in an Expression\n}","tryCatchPattern":"try {\n    return evaluator.evaluate(expression, metadata);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().startsWith(\"Unsupported operand type\")) {\n        logger.warn(\"Unsupported filter operand: {}\", e.getMessage());\n        return false;\n    }\n    throw e;\n}","preventionTips":["Use expression builders or the string DSL instead of hand-constructing operand trees.","Keep spring-ai versions consistent across modules so the DSL and evaluator match."],"tags":["filter-expression","unsupported-type","vector-store"],"backgroundTag":"unsupported-operation","analyzedSha":"98a7beda4f29d80a71c5837eb4053b03a93a46f7","analyzedAt":"2026-09-11T14:15:49.441Z","contentChangedAt":"2026-09-11T14:15:49.441Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}