spring-projects/spring-ai · error · IllegalArgumentException

Expected a Key operand but got:

Error message

Expected a Key operand but got: 

What it means

metadataValue resolves the left side of a comparison and requires a Filter.Key operand naming a metadata field. Any other operand type (Value, Group, or raw Expression) used where a field name is expected throws IllegalArgumentException with the operand's class name.

Source

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

	private Filter.Operand right(Filter.Expression expression) {
		Filter.Operand right = expression.right();
		if (right == null) {
			throw new IllegalArgumentException(
					"Expression of type %s requires a right operand".formatted(expression.type()));
		}
		return right;
	}

	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) {

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. Flip the expression so the metadata field (Filter.Key) is on the left and the literal (Filter.Value) on the right, inverting the operator if needed (a > b -> b < a).
  2. Use Filter.expr("field == value") string parsing, which always produces key-left form.
  3. Add a sanity check that left() instanceof Filter.Key before submitting the filter.
  4. If the right side is the Key and left is the Value, swap operands and swap EQ<->NE or LT<->GT etc.

Example fix

// before
new Filter.Expression(Filter.ExpressionType.GT, new Filter.Value(5), new Filter.Key("age"))
// after
new Filter.Expression(Filter.ExpressionType.LT, new Filter.Key("age"), new Filter.Value(5))
Defensive patterns

Strategy: validation

Validate before calling

static void requireKeyLeft(Filter.Expression e) {
  if (List.of(EQ, NE, GT, GTE, LT, LTE, IN, NIN, ISNULL, ISNOTNULL).contains(e.type())
      && !(e.left() instanceof Filter.Key)) throw new IllegalStateException("Left side must be a metadata Key");
}

Type guard

static boolean isKeyLeft(Filter.Expression e) { return e.left() instanceof Filter.Key; }

Try / catch

try { store.similaritySearch(req); } catch (IllegalArgumentException e) { if (e.getMessage().startsWith("Expected a Key operand")) { /* swap operands and invert operator */ } }

Prevention

When it happens

Trigger: Putting a Filter.Value or Group on the left side of an EQ/NE/GT/LT/IN/ISNULL expression (i.e. comparing in the wrong direction, e.g. Value on the left, Key on the right), then evaluating via S3VectorStore similaritySearch.

Common situations: Reversed comparisons like 5 > age written as filters; filters copied from textual DSLs that permit either side; code generators that place operands inconsistently.

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/83821475aba3176c. Report an issue: GitHub.