{"record":{"id":"50331b7f251578f1","repo":"spring-projects/spring-ai","slug":"non-and-or-isnull-isnotnull-expression-must-have-v","errorCode":null,"errorMessage":"Non AND/OR/ISNULL/ISNOTNULL expression must have Value right argument!","messagePattern":"Non AND/OR/ISNULL/ISNOTNULL expression must have Value right argument!","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"spring-ai-vector-store/src/main/java/org/springframework/ai/vectorstore/filter/converter/AbstractFilterExpressionConverter.java","lineNumber":110,"sourceCode":"\t * @param operand the operand to convert\n\t * @param context the context to append the string representation to\n\t */\n\tprotected void convertOperand(Operand operand, StringBuilder context) {\n\n\t\tif (operand instanceof Filter.Group group) {\n\t\t\tthis.doGroup(group, context);\n\t\t}\n\t\telse if (operand instanceof Filter.Key key) {\n\t\t\tthis.doKey(key, context);\n\t\t}\n\t\telse if (operand instanceof Filter.Value value) {\n\t\t\tthis.doValue(value, context);\n\t\t}\n\t\telse if (operand instanceof Filter.Expression expression) {\n\t\t\tif ((expression.type() != ExpressionType.NOT && expression.type() != ExpressionType.AND\n\t\t\t\t\t&& expression.type() != ExpressionType.OR) && !(expression.right() instanceof Filter.Value)\n\t\t\t\t\t&& !(expression.type() == ExpressionType.ISNULL || expression.type() == ExpressionType.ISNOTNULL)) {\n\t\t\t\tthrow new RuntimeException(\"Non AND/OR/ISNULL/ISNOTNULL expression must have Value right argument!\");\n\t\t\t}\n\t\t\tif (expression.type() == ExpressionType.NOT) {\n\t\t\t\tthis.doNot(expression, context);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tthis.doExpression(expression, context);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Convert the given expression into a string representation.\n\t * @param expression the expression to convert\n\t * @param context the context to append the string representation to\n\t */\n\tprotected void doNot(Filter.Expression expression, StringBuilder context) {\n\t\t// Default behavior is to convert the NOT expression into its semantically\n\t\t// equivalent negation expression.","sourceCodeStart":92,"sourceCodeEnd":128,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/spring-ai-vector-store/src/main/java/org/springframework/ai/vectorstore/filter/converter/AbstractFilterExpressionConverter.java#L92-L128","documentation":"AbstractFilterExpressionConverter.convertOperand validates the shape of every Filter.Expression it converts: any type other than NOT/AND/OR (and except ISNULL/ISNOTNULL which may omit a right side) must have a Filter.Value as its right argument. If not, it throws this RuntimeException, guarding against malformed filter trees being rendered into provider query strings.","triggerScenarios":"Converting a Filter.Expression whose type is e.g. GT/LT/EQ but whose right() is null, a Filter.Key, or another expression; typically from hand-built or deserialized filter trees, reached via convertOperand recursion, doNot, or doGroup.","commonSituations":"Mistakenly swapping left/right when constructing expressions (key on the right); building binary expressions with only a left side; third-party code producing non-canonical trees.","solutions":["Ensure every binary expression has new Filter.Value(...) as its right operand and Filter.Key on the left","Validate the tree before converting: for each non-NOT/AND/OR/ISNULL/ISNOTNULL node assert right() instanceof Filter.Value","Use Filter.builder() or the text parser instead of manual construction so trees are well-formed","Catch RuntimeException around convertExpression and report the offending expression"],"exampleFix":"// before\nnew Filter.Expression(ExpressionType.GT, new Filter.Key(\"price\"), new Filter.Key(\"min\"));\n// after\nnew Filter.Expression(ExpressionType.GT, new Filter.Key(\"price\"), new Filter.Value(minPrice));","handlingStrategy":"validation","validationCode":"boolean wellFormed(Filter.Expression e) { return e.type() == ExpressionType.NOT || e.type() == ExpressionType.AND || e.type() == ExpressionType.OR || e.type() == ExpressionType.ISNULL || e.type() == ExpressionType.ISNOTNULL || e.right() instanceof Filter.Value; }","typeGuard":null,"tryCatchPattern":"try { s = converter.convertExpression(expr); }\ncatch (RuntimeException e) { throw new IllegalStateException(\"Malformed filter tree: \" + e.getMessage(), e); }","preventionTips":["Construct filters via FilterExpressionBuilder or the text parser","Keep Filter.Key on the left and Filter.Value on the right"],"tags":["filter-expression","converter","vector-store"],"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-23T08:17:48.524Z"}