spring-projects/spring-ai · error · IllegalArgumentException

Expected a Value operand but got:

Error message

Expected a Value operand but got: 

What it means

filterValue resolves the right side of a comparison and requires a Filter.Value operand holding the literal. A Filter.Key, Group, or nested Expression in the right-hand position throws IllegalArgumentException naming the actual operand class — the evaluator does not support field-to-field comparisons.

Source

Thrown at vector-stores/spring-ai-s3-vector-store/src/main/java/org/springframework/ai/vectorstore/s3/S3VectorStoreFilterExpressionEvaluator.java:116

	private @Nullable Object metadataValue(Filter.Operand operand, Map<String, Object> metadata) {
		if (operand instanceof Filter.Key key) {
			String k = key.key();
			if (k.length() >= 2
					&& ((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());
	}

	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());
	}

	private int compare(@Nullable Object metaVal, @Nullable Object filterVal) {
		if (metaVal == null && filterVal == null) {
			return 0;
		}
		if (metaVal == null) {
			return -1;
		}
		if (filterVal == null) {
			return 1;
		}
		if (metaVal instanceof Number n1 && filterVal instanceof Number n2) {
			return Double.compare(n1.doubleValue(), n2.doubleValue());
		}
		if (Objects.equals(metaVal, filterVal)) {
			return 0;
		}

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. Use a literal Filter.Value on the right side; compute the comparison value in Java before filtering.
  2. For field-to-field logic, filter results client-side after similaritySearch instead of expressing it in the filter.
  3. Check operand construction: literals must be wrapped with new Filter.Value(x), field names with new Filter.Key("x").
  4. Use Filter.expr(...) to parse the filter string and get correct operand wrapping automatically.

Example fix

// before
new Filter.Expression(Filter.ExpressionType.EQ, new Filter.Key("status"), new Filter.Key("active"))
// after
new Filter.Expression(Filter.ExpressionType.EQ, new Filter.Key("status"), new Filter.Value("active"))
Defensive patterns

Strategy: validation

Validate before calling

static void requireValueRight(Filter.Expression e) {
  if (List.of(EQ, NE, GT, GTE, LT, LTE, IN, NIN).contains(e.type())
      && !(e.right() instanceof Filter.Value)) throw new IllegalStateException("Right side must be a literal Value");
}

Type guard

static boolean isValueRight(Filter.Expression e) { return e.right() instanceof Filter.Value; }

Try / catch

try { store.similaritySearch(req); } catch (IllegalArgumentException e) { if (e.getMessage().startsWith("Expected a Value operand")) { /* replace key-on-right with a computed literal */ } }

Prevention

When it happens

Trigger: Building a comparison whose right operand is another metadata key (field == field), or accidentally wrapping the literal in a Key instead of Value, then running S3VectorStore similaritySearch post-filtering.

Common situations: Attempts at field-to-field filters (e.g. createdAt == updatedAt) unsupported by S3 Vectors post-filter evaluation; constructor argument mix-ups; ports from stores that support key-on-right 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/27d8437db9cc89e6. Report an issue: GitHub.