{"record":{"id":"ca34033678bd4209","repo":"spring-projects/spring-ai","slug":"expected-expression-but-got","errorCode":null,"errorMessage":"Expected Expression but got: ","messagePattern":"Expected Expression but got: ","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"vector-stores/spring-ai-bedrock-knowledgebase-store/src/main/java/org/springframework/ai/vectorstore/bedrockknowledgebase/BedrockKnowledgeBaseFilterExpressionConverter.java","lineNumber":140,"sourceCode":"\tprivate RetrievalFilter convertNotIn(final Expression expression) {\n\t\tFilter.Operand leftOp = Objects.requireNonNull(expression.left(), \"left operand\");\n\t\tFilter.Operand rightOp = Objects.requireNonNull(expression.right(), \"right operand\");\n\t\tString key = ((Key) leftOp).key();\n\t\tList<?> values = extractListValue(rightOp);\n\t\tList<Document> docs = values.stream().map(this::toDocument).toList();\n\t\tFilterAttribute attr = FilterAttribute.builder().key(key).value(Document.fromList(docs)).build();\n\t\treturn RetrievalFilter.builder().notIn(attr).build();\n\t}\n\n\tprivate FilterAttribute createFilterAttribute(final String key, final Object value) {\n\t\treturn FilterAttribute.builder().key(key).value(toDocument(value)).build();\n\t}\n\n\tprivate Expression asExpression(final Filter.Operand operand) {\n\t\tif (operand instanceof Expression expr) {\n\t\t\treturn expr;\n\t\t}\n\t\tthrow new IllegalArgumentException(\"Expected Expression but got: \" + operand.getClass());\n\t}\n\n\tprivate Object extractValue(final Filter.Operand operand) {\n\t\tif (operand instanceof Value value) {\n\t\t\treturn value.value();\n\t\t}\n\t\tthrow new IllegalArgumentException(\"Expected Value but got: \" + operand.getClass());\n\t}\n\n\tprivate List<?> extractListValue(final Filter.Operand operand) {\n\t\tObject value = extractValue(operand);\n\t\tif (value instanceof List) {\n\t\t\treturn (List<?>) value;\n\t\t}\n\t\tthrow new IllegalArgumentException(\"Expected List for IN/NIN but got: \" + value.getClass());\n\t}\n\n\tprivate Document toDocument(final Object value) {","sourceCodeStart":122,"sourceCodeEnd":158,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/vector-stores/spring-ai-bedrock-knowledgebase-store/src/main/java/org/springframework/ai/vectorstore/bedrockknowledgebase/BedrockKnowledgeBaseFilterExpressionConverter.java#L122-L158","documentation":"asExpression narrows a Filter.Operand to an Expression when reading the left/right side of an AND/OR group in BedrockKnowledgeBaseFilterExpressionConverter. If the operand is a Value instead (meaning the group has a literal where a sub-expression is required), it throws an IllegalArgumentException with the operand's actual class. This protects the recursive conversion of nested boolean trees.","triggerScenarios":"Building a filter where an operand of AND/OR is a raw Filter.Value — e.g. Filter.and(new Value(true), expr) or a mis-constructed tree — then converting it or running similaritySearch with that filterExpression.","commonSituations":"Hand-assembling Filter operand lists programmatically and accidentally including values; generic query-builder code that mixes operands and expressions; version drift where the Filter model allowed operand shapes the Bedrock converter does not accept.","solutions":["Ensure every operand of AND/OR/NOT groups is itself a Filter.Expression (comparison or group), never a Value","Move literal values into comparison expressions: GT(key, value) instead of passing the value directly as a group operand","Inspect the expression tree before conversion and reject/misuse-check operands with instanceof Expression","Catch IllegalArgumentException around conversion and log the offending operand class"],"exampleFix":"// before\nFilter.Operand bad = new Value(List.of(1,2,3));\nExpression e = AND(bad, expr); // left operand is a Value\n// after\nFilter.Operand good = IN(new ExpressionText(\"tag\"), List.of(1,2,3));\nExpression e = AND(good, expr); // operands must be Expressions","handlingStrategy":"type-guard","validationCode":"for (Filter.Operand op : List.of(expr.left(), expr.right())) { if (op != null && !(op instanceof Expression) && isGroupType(expr.type())) { throw new IllegalArgumentException(\"Group operands must be Expressions\"); } }","typeGuard":"Expression requireExpression(Filter.Operand o) { if (o instanceof Expression e) return e; throw new IllegalArgumentException(\"Expected Expression operand, got: \" + (o == null ? \"null\" : o.getClass())); }","tryCatchPattern":"try { String filter = converter.convertExpression(expression); ... } catch (IllegalArgumentException e) { log.error(\"Invalid filter tree: {}\", e.getMessage()); throw new InvalidFilterException(e); }","preventionTips":["Build groups only from Expressions, never raw Values","Use the Filter helper DSL instead of hand-assembling operands","Validate the filter tree before conversion","Avoid generic query builders that mix operand kinds"],"tags":["bedrock","filter-expression","type-mismatch","aws"],"backgroundTag":"type-mismatch","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"}