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
- Ensure every operand of AND/OR/NOT groups is itself a Filter.Expression (comparison or group), never a Value
- Move literal values into comparison expressions: GT(key, value) instead of passing the value directly as a group operand
- Inspect the expression tree before conversion and reject/misuse-check operands with instanceof Expression
- 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
- Build groups only from Expressions, never raw Values
- Use the Filter helper DSL instead of hand-assembling operands
- Validate the filter tree before conversion
- Avoid generic query builders that mix operand kinds
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
- Expected Value but got:
- Filter type not supported:
- NOT operator negation failed for expression type: . Operand:
- Expected List for IN/NIN but got:
- The region '<region>' is not a valid region!
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/ca34033678bd4209.
Report an issue: GitHub.