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

  1. Inspect the logged class name and build filters only via supported DSL operators (and/or/not/eq/gt/lt/in etc.).
  2. Do not pass Filter.Key/Filter.Value to evaluateOperand directly; wrap them in a comparison Expression.
  3. 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

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


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