{"record":{"id":"aa0d1866edaec013","repo":"spring-projects/spring-ai","slug":"not-operator-negation-failed-for-expression-type","errorCode":null,"errorMessage":"NOT operator negation failed for expression type: . Operand: ","messagePattern":"NOT operator negation failed for expression type: \\. Operand: ","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":91,"sourceCode":"\t\tRetrievalFilter left = convert(asExpression(leftOp));\n\t\tRetrievalFilter right = convert(asExpression(rightOp));\n\t\treturn RetrievalFilter.builder().andAll(left, right).build();\n\t}\n\n\tprivate RetrievalFilter convertOr(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\tRetrievalFilter left = convert(asExpression(leftOp));\n\t\tRetrievalFilter right = convert(asExpression(rightOp));\n\t\treturn RetrievalFilter.builder().orAll(left, right).build();\n\t}\n\n\tprivate RetrievalFilter convertNot(final Expression expression) {\n\t\tFilter.Operand negated = FilterHelper.negate(expression);\n\t\tif (negated instanceof Expression negatedExpr) {\n\t\t\treturn convert(negatedExpr);\n\t\t}\n\t\tthrow new IllegalArgumentException(\n\t\t\t\t\"NOT operator negation failed for expression type: \" + expression.type() + \". Operand: \" + negated);\n\t}\n\n\tprivate RetrievalFilter buildComparison(final Expression exp, final ComparisonOp op) {\n\t\tFilter.Operand leftOp = Objects.requireNonNull(exp.left(), \"left operand\");\n\t\tFilter.Operand rightOp = Objects.requireNonNull(exp.right(), \"right operand\");\n\t\tString key = ((Key) leftOp).key();\n\t\tObject value = extractValue(rightOp);\n\t\tFilterAttribute attr = createFilterAttribute(key, value);\n\n\t\treturn switch (op) {\n\t\t\tcase EQ -> RetrievalFilter.builder().equalsValue(attr).build();\n\t\t\tcase NE -> RetrievalFilter.builder().notEquals(attr).build();\n\t\t\tcase GT -> RetrievalFilter.builder().greaterThan(attr).build();\n\t\t\tcase GTE -> RetrievalFilter.builder().greaterThanOrEquals(attr).build();\n\t\t\tcase LT -> RetrievalFilter.builder().lessThan(attr).build();\n\t\t\tcase LTE -> RetrievalFilter.builder().lessThanOrEquals(attr).build();\n\t\t};","sourceCodeStart":73,"sourceCodeEnd":109,"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#L73-L109","documentation":"convertNot uses FilterHelper.negate(expression) to turn NOT X into a directly expressible form; if negation returns a plain Value operand rather than an Expression (e.g. negating something the helper cannot structurally invert), the converter cannot recurse and throws an IllegalArgumentException describing the original type and operand. This is an internal invariant guard within NOT handling.","triggerScenarios":"Converting a filter like NOT(value) or NOT(IN(...)) whose negation by FilterHelper yields a non-Expression operand (e.g. a boolean Value) instead of an invertible sub-expression, via BedrockKnowledgeBaseVectorStore.similaritySearch with such a filterExpression or direct convertNot call.","commonSituations":"Wrapping a bare value or unsupported leaf in NOT(); library version mismatch where FilterHelper.negate changed return behavior; hand-built expression trees with NOT around operands that are not AND/OR/comparison expressions.","solutions":["Apply NOT only to group or comparison expressions (NOT(AND(...)), NOT(GT(...)), NOT(IN(...))) rather than bare values","Rewrite the filter manually: replace NOT(X) with the logically inverted expression using supported operators","Ensure Spring AI core and the Bedrock store versions are aligned so FilterHelper.negate behaves as expected","Catch IllegalArgumentException from similaritySearch and fall back to no-filter search plus client-side filtering"],"exampleFix":"// before\nExpression e = NOT(new Value(true)); // negation yields a Value, not Expression\n// after\nExpression e = EQ(new ExpressionText(\"flag\"), false); // express the intent directly without NOT","handlingStrategy":"validation","validationCode":"if (expr.type() == ExpressionType.NOT) { Filter.Operand inner = expr.left(); if (!(inner instanceof Expression)) { throw new IllegalArgumentException(\"NOT must wrap a sub-expression, got: \" + inner); } }","typeGuard":"boolean notWrapsExpression(Filter.Operand o) { return o instanceof Expression e && e.type() != ExpressionType.NOT || (e != null && e.left() instanceof Expression); }","tryCatchPattern":"try { String filter = converter.convertExpression(expression); ... } catch (IllegalArgumentException e) { log.warn(\"NOT negation failed: {}\", e.getMessage()); /* rewrite or drop the NOT */ }","preventionTips":["Only wrap AND/OR/comparison expressions in NOT","Manually invert filters instead of relying on NOT when possible","Keep Spring AI core aligned with the Bedrock store version so FilterHelper.negate behaves consistently","Test NOT-containing filters against the converter in unit tests"],"tags":["bedrock","filter-expression","not-operator","aws"],"backgroundTag":"internal-invariant-violation","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"}