spring-projects/spring-ai · error · RuntimeException
Non AND/OR/ISNULL/ISNOTNULL expression must have Value…
Error message
Non AND/OR/ISNULL/ISNOTNULL expression must have Value right argument!
What it means
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.
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
Example fix
// before
new Filter.Expression(ExpressionType.GT, new Filter.Key("price"), new Filter.Key("min"));
// after
new Filter.Expression(ExpressionType.GT, new Filter.Key("price"), new Filter.Value(minPrice)); Defensive patterns
Strategy: validation
Validate before calling
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; } Try / catch
try { s = converter.convertExpression(expr); }
catch (RuntimeException e) { throw new IllegalStateException("Malformed filter tree: " + e.getMessage(), e); } Prevention
- Construct filters via FilterExpressionBuilder or the text parser
- Keep Filter.Key on the left and Filter.Value on the right
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- Cannot compare values of incompatible types
- Cannot compare values of incompatible types
- Cannot compare values of types
- Cannot compare values of types
- Expected a Key operand but got:
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/50331b7f251578f1.
Report an issue: GitHub.
Appendix: source
Thrown at spring-ai-vector-store/src/main/java/org/springframework/ai/vectorstore/filter/converter/AbstractFilterExpressionConverter.java:110
* @param operand the operand to convert
* @param context the context to append the string representation to
*/
protected void convertOperand(Operand operand, StringBuilder context) {
if (operand instanceof Filter.Group group) {
this.doGroup(group, context);
}
else if (operand instanceof Filter.Key key) {
this.doKey(key, context);
}
else if (operand instanceof Filter.Value value) {
this.doValue(value, context);
}
else if (operand instanceof Filter.Expression expression) {
if ((expression.type() != ExpressionType.NOT && expression.type() != ExpressionType.AND
&& expression.type() != ExpressionType.OR) && !(expression.right() instanceof Filter.Value)
&& !(expression.type() == ExpressionType.ISNULL || expression.type() == ExpressionType.ISNOTNULL)) {
throw new RuntimeException("Non AND/OR/ISNULL/ISNOTNULL expression must have Value right argument!");
}
if (expression.type() == ExpressionType.NOT) {
this.doNot(expression, context);
}
else {
this.doExpression(expression, context);
}
}
}
/**
* Convert the given expression into a string representation.
* @param expression the expression to convert
* @param context the context to append the string representation to
*/
protected void doNot(Filter.Expression expression, StringBuilder context) {
// Default behavior is to convert the NOT expression into its semantically
// equivalent negation expression.View on GitHub (pinned to 98a7beda4f)