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

  1. 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"))).
  2. Use ExpressionType.AND/OR/NOT only for grouping sub-expressions; use EQ/NE/GT/GTE/LT/LTE/IN/NIN for value comparisons.
  3. Build filters via the FluentFilter DSL (Filter.builder().eq("genre","drama").build()) instead of hand-assembling Expression objects.
  4. 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

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


AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11). Data as JSON: /api/errors/ef64904c0ff38cfa. Report an issue: GitHub.