spring-projects/spring-ai · error · RuntimeException
Non AND/OR/NOT expression must have Value right argument!
Error message
Non AND/OR/NOT expression must have Value right argument!
What it means
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(...).
Source
Thrown at vector-stores/spring-ai-qdrant-store/src/main/java/org/springframework/ai/vectorstore/qdrant/QdrantFilterExpressionConverter.java:66
List<Condition> mustNotClauses = new ArrayList<>();
if (operand instanceof Expression expression) {
if (expression.type() == ExpressionType.NOT && expression.left() instanceof Group group) {
mustNotClauses.add(io.qdrant.client.ConditionFactory.filter(convertOperand(group.content())));
}
else if (expression.type() == ExpressionType.AND) {
Assert.state(expression.right() != null, "expected an expression with a right operand");
mustClauses.add(io.qdrant.client.ConditionFactory.filter(convertOperand(expression.left())));
mustClauses.add(io.qdrant.client.ConditionFactory.filter(convertOperand(expression.right())));
}
else if (expression.type() == ExpressionType.OR) {
Assert.state(expression.right() != null, "expected an expression with a right operand");
shouldClauses.add(io.qdrant.client.ConditionFactory.filter(convertOperand(expression.left())));
shouldClauses.add(io.qdrant.client.ConditionFactory.filter(convertOperand(expression.right())));
}
else {
if (!(expression.right() instanceof Value)) {
throw new RuntimeException("Non AND/OR/NOT expression must have Value right argument!");
}
mustClauses.add(parseComparison((Key) expression.left(), (Value) expression.right(), expression));
}
}
return context.addAllMust(mustClauses).addAllShould(shouldClauses).addAllMustNot(mustNotClauses).build();
}
protected Condition parseComparison(Key key, Value value, Expression exp) {
ExpressionType type = exp.type();
return switch (type) {
case EQ -> buildEqCondition(key, value);
case NE -> buildNeCondition(key, value);
case GT -> buildGtCondition(key, value);
case GTE -> buildGteCondition(key, value);
case LT -> buildLtCondition(key, value);View on GitHub (pinned to 98a7beda4f)
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).
Example fix
// before
new Filter.Expression(ExpressionType.AND, expA, new Filter.Expression(ExpressionType.GT, new Key("year"), expB));
// after
new Filter.Expression(ExpressionType.GT, new Key("year"), new Value(2020)); Defensive patterns
Strategy: validation
Validate before calling
// Java
static void validate(Filter.Expression e) {
if (e == null) return;
var t = e.type();
if (t == ExpressionType.AND || t == ExpressionType.OR || t == ExpressionType.NOT) {
validate(e.left()); validate(e.right());
} else {
if (!(e.right() instanceof Value)) {
throw new IllegalArgumentException("Leaf expression must have Value right operand: " + t);
}
}
}
validate(filterExpression); Type guard
static boolean isLeaf(Filter.Expression e) {
return e.type() != ExpressionType.AND && e.type() != ExpressionType.OR && e.type() != ExpressionType.NOT;
}
// before passing to Qdrant: assert !isLeaf(e) || e.right() instanceof Value; Try / catch
try {
vectorStore.similaritySearch(SearchRequest.builder().query(q).filterExpression(expr).build());
} catch (RuntimeException ex) {
if (ex.getMessage() != null && ex.getMessage().contains("must have Value right argument")) {
throw new IllegalArgumentException("Malformed filter expression", ex);
}
throw ex;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.
Related errors
- Unsupported value in NIN value list. Only supports String or
- Unsupported value type for NIN condition. Only supports non-
- Expression of type %s requires a left operand
- Expression of type %s requires a right operand
- Expected a Key operand but got:
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/ef64904c0ff38cfa.
Report an issue: GitHub.