{"record":{"id":"ef64904c0ff38cfa","repo":"spring-projects/spring-ai","slug":"non-and-or-not-expression-must-have-value-right-ar","errorCode":null,"errorMessage":"Non AND/OR/NOT expression must have Value right argument!","messagePattern":"Non AND/OR/NOT expression must have Value right argument!","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"vector-stores/spring-ai-qdrant-store/src/main/java/org/springframework/ai/vectorstore/qdrant/QdrantFilterExpressionConverter.java","lineNumber":66,"sourceCode":"\t\tList<Condition> mustNotClauses = new ArrayList<>();\n\n\t\tif (operand instanceof Expression expression) {\n\t\t\tif (expression.type() == ExpressionType.NOT && expression.left() instanceof Group group) {\n\t\t\t\tmustNotClauses.add(io.qdrant.client.ConditionFactory.filter(convertOperand(group.content())));\n\t\t\t}\n\t\t\telse if (expression.type() == ExpressionType.AND) {\n\t\t\t\tAssert.state(expression.right() != null, \"expected an expression with a right operand\");\n\t\t\t\tmustClauses.add(io.qdrant.client.ConditionFactory.filter(convertOperand(expression.left())));\n\t\t\t\tmustClauses.add(io.qdrant.client.ConditionFactory.filter(convertOperand(expression.right())));\n\t\t\t}\n\t\t\telse if (expression.type() == ExpressionType.OR) {\n\t\t\t\tAssert.state(expression.right() != null, \"expected an expression with a right operand\");\n\t\t\t\tshouldClauses.add(io.qdrant.client.ConditionFactory.filter(convertOperand(expression.left())));\n\t\t\t\tshouldClauses.add(io.qdrant.client.ConditionFactory.filter(convertOperand(expression.right())));\n\t\t\t}\n\t\t\telse {\n\t\t\t\tif (!(expression.right() instanceof Value)) {\n\t\t\t\t\tthrow new RuntimeException(\"Non AND/OR/NOT expression must have Value right argument!\");\n\t\t\t\t}\n\t\t\t\tmustClauses.add(parseComparison((Key) expression.left(), (Value) expression.right(), expression));\n\t\t\t}\n\n\t\t}\n\n\t\treturn context.addAllMust(mustClauses).addAllShould(shouldClauses).addAllMustNot(mustNotClauses).build();\n\t}\n\n\tprotected Condition parseComparison(Key key, Value value, Expression exp) {\n\n\t\tExpressionType type = exp.type();\n\t\treturn switch (type) {\n\t\t\tcase EQ -> buildEqCondition(key, value);\n\t\t\tcase NE -> buildNeCondition(key, value);\n\t\t\tcase GT -> buildGtCondition(key, value);\n\t\t\tcase GTE -> buildGteCondition(key, value);\n\t\t\tcase LT -> buildLtCondition(key, value);","sourceCodeStart":48,"sourceCodeEnd":84,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/vector-stores/spring-ai-qdrant-store/src/main/java/org/springframework/ai/vectorstore/qdrant/QdrantFilterExpressionConverter.java#L48-L84","documentation":"Spring AI's Filter.Expression is generic: the right operand of a group expression must itself be a group (AND/OR/NOT), while any other expression type must carry a plain Value on the right. QdrantFilterExpressionConverter.convertOperand hits the else branch for a non-group expression whose right() is not a Value and throws. This is an invalid filter expression shape passed to SearchRequest.builder().filterExpression(...).","triggerScenarios":"Calling SearchRequest.builder().filterExpression() with an expression such as Expression.not(value) nested incorrectly, a Group containing a non-Value right operand (e.g. `exp.gt(\"a\",1).and(exp.gt(\"b\",2))` built with a binary op whose right is another Expression but whose type is neither AND, OR, nor NOT), or a custom/hand-constructed Filter.Expression with wrong operand kinds.","commonSituations":"Developers programmatically composing filters (e.g. combining two comparison expressions with an unsupported operator constant, or wrapping a statement in Group) and passing the result to the Qdrant vector store; also happens after upgrading Spring AI when manual expression construction code no longer matches the expected shape.","solutions":["Ensure every non-AND/OR/NOT Filter.Expression has a Value instance as its right argument (e.g. new Filter.Expression(ExpressionType.EQ, new Key(\"genre\"), new Value(\"drama\"))).","Use ExpressionType.AND/OR/NOT only for grouping sub-expressions; use EQ/NE/GT/GTE/LT/LTE/IN/NIN for value comparisons.","Build filters via the FluentFilter DSL (Filter.builder().eq(\"genre\",\"drama\").build()) instead of hand-assembling Expression objects.","Validate the expression tree before passing it (walk it and assert leaf nodes have Value right operands)."],"exampleFix":"// before\nnew Filter.Expression(ExpressionType.AND, expA, new Filter.Expression(ExpressionType.GT, new Key(\"year\"), expB));\n// after\nnew Filter.Expression(ExpressionType.GT, new Key(\"year\"), new Value(2020));","handlingStrategy":"validation","validationCode":"// Java\nstatic void validate(Filter.Expression e) {\n    if (e == null) return;\n    var t = e.type();\n    if (t == ExpressionType.AND || t == ExpressionType.OR || t == ExpressionType.NOT) {\n        validate(e.left()); validate(e.right());\n    } else {\n        if (!(e.right() instanceof Value)) {\n            throw new IllegalArgumentException(\"Leaf expression must have Value right operand: \" + t);\n        }\n    }\n}\nvalidate(filterExpression);","typeGuard":"static boolean isLeaf(Filter.Expression e) {\n    return e.type() != ExpressionType.AND && e.type() != ExpressionType.OR && e.type() != ExpressionType.NOT;\n}\n// before passing to Qdrant: assert !isLeaf(e) || e.right() instanceof Value;","tryCatchPattern":"try {\n    vectorStore.similaritySearch(SearchRequest.builder().query(q).filterExpression(expr).build());\n} catch (RuntimeException ex) {\n    if (ex.getMessage() != null && ex.getMessage().contains(\"must have Value right argument\")) {\n        throw new IllegalArgumentException(\"Malformed filter expression\", ex);\n    }\n    throw ex;\n}","preventionTips":["Build filters with the FluentFilter/Filter.builder DSL rather than raw Expression constructors.","Keep group operators (AND/OR/NOT) reserved for sub-expressions only.","Add a unit test that walks every filter your app can produce."],"tags":["qdrant","filter-expression","vector-store","spring-ai"],"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"}