spring-projects/spring-ai · error · IllegalArgumentException

Expected a Value operand but got:

Error message

Expected a Value operand but got: 

What it means

The evaluator's filterValue() expects the right operand of a comparison to be a Filter.Value holding a constant. If it receives a Key (or another Operand type), it throws IllegalArgumentException with the operand's class name. The right side of a comparison must be a literal value, not a metadata key.

Source

Thrown at spring-ai-vector-store/src/main/java/org/springframework/ai/vectorstore/SimpleVectorStoreFilterExpressionEvaluator.java:168

					&& ((k.startsWith("\"") && k.endsWith("\"")) || (k.startsWith("'") && k.endsWith("'")))) {
				k = k.substring(1, k.length() - 1);
			}
			return metadata.get(k);
		}
		throw new IllegalArgumentException("Expected a Key operand but got: " + operand.getClass().getName());
	}

	/**
	 * Extracts the constant value from a {@link Filter.Value} operand. {@link Date}
	 * instances are formatted to their ISO-8601 UTC string so they can be compared
	 * directly with metadata strings stored in the same format.
	 */
	private Object filterValue(Filter.Operand operand) {
		if (operand instanceof Filter.Value filterValue) {
			Object value = filterValue.value();
			return (value instanceof Date date) ? DATE_FORMATTER.format(date.toInstant()) : value;
		}
		throw new IllegalArgumentException("Expected a Value operand but got: " + operand.getClass().getName());
	}

	/**
	 * Compares two values. Numbers are promoted to {@code double} to allow cross-type
	 * numeric comparison (e.g. {@code Integer} vs {@code Double}). All other
	 * {@link Comparable} types are compared directly.
	 *
	 * <p>
	 * Null ordering follows SQL {@code NULLS FIRST} semantics: {@code null} is considered
	 * less than any non-null value. As a result, a missing metadata key causes ordered
	 * comparisons ({@code GT}, {@code GTE}, {@code LT}, {@code LTE}) to behave as if the
	 * key holds the smallest possible value — e.g. {@code year > 2020} returns
	 * {@code false} when {@code year} is absent.
	 */
	@SuppressWarnings("unchecked")
	private int compare(@Nullable Object metaVal, @Nullable Object filterVal) {
		if (metaVal == null && filterVal == null) {
			return 0;

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. Wrap the constant in a Filter.Value on the right side of the expression.
  2. Cross-field metadata comparison is unsupported — compute the comparison before building the filter.
  3. Verify the parsed expression's right operand is a Filter.Value before search.

Example fix

// before
new Filter.Expression(EQ, new Filter.Key("color"), new Filter.Key("shade"));
// after
new Filter.Expression(EQ, new Filter.Key("color"), new Filter.Value("shade"));
Defensive patterns

Strategy: validation

Validate before calling

boolean rightIsValue(Filter.Expression e) {
    return e != null && e.right() instanceof Filter.Value;
}

Type guard

Filter.Value asValue(Filter.Operand op) { return op instanceof Filter.Value v ? v : null; }

Try / catch

try {
    vectorStore.similaritySearch(request);
} catch (IllegalArgumentException ex) {
    if (ex.getMessage().startsWith("Expected a Value operand")) {
        // wrap the right operand's constant in Filter.Value
    } else throw ex;
}

Prevention

When it happens

Trigger: A Filter.Expression like EQ(key, key) — two metadata keys compared — evaluated by SimpleVectorStore; nested Filter.Expression where a Value was expected.

Common situations: Hand-building expressions comparing two metadata fields; forgetting to wrap the constant in Filter.Value; translating SQL-style key-to-key comparisons.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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