spring-projects/spring-ai · error · IllegalArgumentException

Expected a Key operand but got:

Error message

Expected a Key operand but got: 

What it means

The evaluator's metadataValue() expects the left operand of a comparison to be a Filter.Key naming a metadata field. If the operand is a Value (or any other Operand type), it throws IllegalArgumentException including the actual class name. Metadata comparisons must be keyed on document metadata, not constants on the left side.

Source

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

		}
		return right;
	}

	/**
	 * Extracts the metadata value for the given {@link Filter.Key} operand. Outer quotes
	 * ({@code "..."} or {@code '...'}) are stripped from the key name to match the format
	 * used by {@link FilterExpressionBuilder} and the text parser.
	 */
	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());
	}

	/**
	 * 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

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. Make the metadata field a Filter.Key on the left side and the constant a Filter.Value on the right.
  2. If using the text parser, write the filter as 'color == \'green\'' not '\'green\' == color'.
  3. Inspect the parsed expression tree (Filter.Expression#left) to confirm the left operand is a Key.

Example fix

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

Strategy: validation

Validate before calling

boolean leftIsKey(Filter.Expression e) {
    return e != null && e.left() instanceof Filter.Key;
}

Type guard

Filter.Key asKey(Filter.Operand op) { return op instanceof Filter.Key k ? k : null; }

Try / catch

try {
    vectorStore.similaritySearch(request);
} catch (IllegalArgumentException ex) {
    if (ex.getMessage().startsWith("Expected a Key operand")) {
        // swap operands: metadata key must be on the left
    } else throw ex;
}

Prevention

When it happens

Trigger: A Filter.Expression like EQ(value, value) or EQ(value, key) — i.e. a Filter.Value used where a Filter.Key is expected — evaluated against SimpleVectorStore.

Common situations: Swapping operand order when hand-building expressions; string filters like "'green' == color" instead of "color == 'green'"; code generation producing reversed 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/878589c6fae86745. Report an issue: GitHub.