spring-projects/spring-ai · error · IllegalArgumentException
Unsupported operand type:
Error message
Unsupported operand type:
What it means
SimpleVectorStoreFilterExpressionEvaluator.evaluateOperand throws IllegalArgumentException 'Unsupported operand type: <class name>' when a Filter.Operand passed to it is not a Filter.Expression, Filter.Key, or Filter.Value. Per the source, Key/Value leaves are consumed inside evaluateExpression, so reaching this throw means the expression tree contains an operand type the evaluator does not recognize.
Source
Thrown at spring-ai-vector-store/src/main/java/org/springframework/ai/vectorstore/SimpleVectorStoreFilterExpressionEvaluator.java:91
* @param metadata the document metadata to match against; must not be {@code null}
* @return {@code true} if the metadata satisfies the expression
*/
public boolean evaluate(Filter.Expression expression, Map<String, Object> metadata) {
return evaluateExpression(expression, metadata);
}
private boolean evaluateOperand(Filter.Operand operand, Map<String, Object> metadata) {
if (operand instanceof Filter.Group group) {
return evaluateOperand(group.content(), metadata);
}
if (operand instanceof Filter.Expression expression) {
return evaluateExpression(expression, metadata);
}
// Filter.Key and Filter.Value are leaf operands consumed directly by
// metadataValue() and filterValue() inside evaluateExpression(). They are never
// passed here as top-level boolean operands, so this branch is unreachable under
// normal usage.
throw new IllegalArgumentException("Unsupported operand type: " + operand.getClass().getName());
}
private boolean evaluateExpression(Filter.Expression expression, Map<String, Object> metadata) {
return switch (expression.type()) {
case AND -> evaluateOperand(left(expression), metadata) && evaluateOperand(right(expression), metadata);
case OR -> evaluateOperand(left(expression), metadata) || evaluateOperand(right(expression), metadata);
// Unary operator: only the left operand is used. Ignore right operand
case NOT -> !evaluateOperand(left(expression), metadata);
case EQ -> compare(metadataValue(left(expression), metadata), filterValue(right(expression))) == 0;
case NE -> compare(metadataValue(left(expression), metadata), filterValue(right(expression))) != 0;
case GT -> compare(metadataValue(left(expression), metadata), filterValue(right(expression))) > 0;
case GTE -> compare(metadataValue(left(expression), metadata), filterValue(right(expression))) >= 0;
case LT -> compare(metadataValue(left(expression), metadata), filterValue(right(expression))) < 0;
case LTE -> compare(metadataValue(left(expression), metadata), filterValue(right(expression))) <= 0;
case IN -> {
Object metaVal = metadataValue(left(expression), metadata);
List<?> list = asList(filterValue(right(expression)), expression);
yield list.stream().anyMatch(item -> compare(metaVal, item) == 0);View on GitHub (pinned to 98a7beda4f)
Solutions
- Inspect the logged class name and build filters only via supported DSL operators (and/or/not/eq/gt/lt/in etc.).
- Do not pass Filter.Key/Filter.Value to evaluateOperand directly; wrap them in a comparison Expression.
- Upgrade spring-ai so the evaluator recognizes any newly introduced operand types.
Example fix
// before
evaluator.evaluateOperand(new Filter.Key("genre"), metadata); // unsupported here
// after
Filter.Expression expr = new Filter.Expression(ExpressionType.EQ,
new Filter.Key("genre"), new Filter.Value("sci-fi"));
evaluator.evaluate(expr, metadata); Defensive patterns
Strategy: validation
Validate before calling
// build filters only with supported DSL operators
SearchRequest request = SearchRequest.query(q)
.withFilterExpression("genre == 'sci-fi' and year > 2020"); Type guard
static boolean isSupportedOperand(Filter.Operand o) {
return o instanceof Filter.Expression; // Key/Value must be wrapped in an Expression
} Try / catch
try {
return evaluator.evaluate(expression, metadata);
} catch (IllegalArgumentException e) {
if (e.getMessage().startsWith("Unsupported operand type")) {
logger.warn("Unsupported filter operand: {}", e.getMessage());
return false;
}
throw e;
} Prevention
- Use expression builders or the string DSL instead of hand-constructing operand trees.
- Keep spring-ai versions consistent across modules so the DSL and evaluator match.
When it happens
Trigger: Building a Filter expression tree programmatically with a custom or unanticipated Operand subclass; calling evaluateOperand directly with a Filter.Key or Filter.Value; a newer Vector Search DSL introducing operand types the evaluator does not handle.
Common situations: Custom filter construction instead of using Filter expression builders (eq, gt, and, or...); version drift between the search DSL and the store's evaluator.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- Unknown message type:
- Expression of type %s requires a left operand
- Expression of type %s requires a right operand
- Expected a Key operand but got:
- Expected a Value operand but got:
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/cd0b405ccd71f917.
Report an issue: GitHub.