spring-projects/spring-ai · error · IllegalArgumentException

Expected Expression but got:

Error message

Expected Expression but got: 

What it means

asExpression narrows a Filter.Operand to an Expression when reading the left/right side of an AND/OR group in BedrockKnowledgeBaseFilterExpressionConverter. If the operand is a Value instead (meaning the group has a literal where a sub-expression is required), it throws an IllegalArgumentException with the operand's actual class. This protects the recursive conversion of nested boolean trees.

Source

Thrown at vector-stores/spring-ai-bedrock-knowledgebase-store/src/main/java/org/springframework/ai/vectorstore/bedrockknowledgebase/BedrockKnowledgeBaseFilterExpressionConverter.java:140

	private RetrievalFilter convertNotIn(final Expression expression) {
		Filter.Operand leftOp = Objects.requireNonNull(expression.left(), "left operand");
		Filter.Operand rightOp = Objects.requireNonNull(expression.right(), "right operand");
		String key = ((Key) leftOp).key();
		List<?> values = extractListValue(rightOp);
		List<Document> docs = values.stream().map(this::toDocument).toList();
		FilterAttribute attr = FilterAttribute.builder().key(key).value(Document.fromList(docs)).build();
		return RetrievalFilter.builder().notIn(attr).build();
	}

	private FilterAttribute createFilterAttribute(final String key, final Object value) {
		return FilterAttribute.builder().key(key).value(toDocument(value)).build();
	}

	private Expression asExpression(final Filter.Operand operand) {
		if (operand instanceof Expression expr) {
			return expr;
		}
		throw new IllegalArgumentException("Expected Expression but got: " + operand.getClass());
	}

	private Object extractValue(final Filter.Operand operand) {
		if (operand instanceof Value value) {
			return value.value();
		}
		throw new IllegalArgumentException("Expected Value but got: " + operand.getClass());
	}

	private List<?> extractListValue(final Filter.Operand operand) {
		Object value = extractValue(operand);
		if (value instanceof List) {
			return (List<?>) value;
		}
		throw new IllegalArgumentException("Expected List for IN/NIN but got: " + value.getClass());
	}

	private Document toDocument(final Object value) {

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. Ensure every operand of AND/OR/NOT groups is itself a Filter.Expression (comparison or group), never a Value
  2. Move literal values into comparison expressions: GT(key, value) instead of passing the value directly as a group operand
  3. Inspect the expression tree before conversion and reject/misuse-check operands with instanceof Expression
  4. Catch IllegalArgumentException around conversion and log the offending operand class

Example fix

// before
Filter.Operand bad = new Value(List.of(1,2,3));
Expression e = AND(bad, expr); // left operand is a Value
// after
Filter.Operand good = IN(new ExpressionText("tag"), List.of(1,2,3));
Expression e = AND(good, expr); // operands must be Expressions
Defensive patterns

Strategy: type-guard

Validate before calling

for (Filter.Operand op : List.of(expr.left(), expr.right())) { if (op != null && !(op instanceof Expression) && isGroupType(expr.type())) { throw new IllegalArgumentException("Group operands must be Expressions"); } }

Type guard

Expression requireExpression(Filter.Operand o) { if (o instanceof Expression e) return e; throw new IllegalArgumentException("Expected Expression operand, got: " + (o == null ? "null" : o.getClass())); }

Try / catch

try { String filter = converter.convertExpression(expression); ... } catch (IllegalArgumentException e) { log.error("Invalid filter tree: {}", e.getMessage()); throw new InvalidFilterException(e); }

Prevention

When it happens

Trigger: Building a filter where an operand of AND/OR is a raw Filter.Value — e.g. Filter.and(new Value(true), expr) or a mis-constructed tree — then converting it or running similaritySearch with that filterExpression.

Common situations: Hand-assembling Filter operand lists programmatically and accidentally including values; generic query-builder code that mixes operands and expressions; version drift where the Filter model allowed operand shapes the Bedrock converter does not accept.

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/ca34033678bd4209. Report an issue: GitHub.