spring-projects/spring-ai · error · IllegalArgumentException

Expression of type %s requires a left operand

Error message

Expression of type %s requires a left operand

What it means

The evaluator's left() accessor throws IllegalArgumentException when a Filter.Expression has no left operand but the expression type requires one. All operators evaluated by evaluateExpression (AND, OR, NOT, comparisons, IN/NIN, ISNULL/ISNOTNULL) need a left side, so a null left operand is treated as a malformed filter.

Source

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

			case IN -> {
				Object metaVal = metadataValue(left(expression), metadata);
				List<?> list = asList(filterValue(right(expression)), expression);
				yield list.stream().anyMatch(item -> compare(metaVal, item) == 0);
			}
			case NIN -> {
				Object metaVal = metadataValue(left(expression), metadata);
				List<?> list = asList(filterValue(right(expression)), expression);
				yield list.stream().noneMatch(item -> compare(metaVal, item) == 0);
			}
			case ISNULL -> metadataValue(left(expression), metadata) == null;
			case ISNOTNULL -> metadataValue(left(expression), metadata) != null;
		};
	}

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

	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

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. For NOT, place the negated expression as the LEFT operand: new Filter.Expression(NOT, groupOrExpression, null).
  2. Use the Filter builder/expr helper to construct expressions so operand placement is correct.
  3. Before evaluation, assert expression.left() != null for every node you build programmatically.
  4. Catch IllegalArgumentException around similaritySearch and inspect the type named in the message.

Example fix

// before
new Filter.Expression(Filter.ExpressionType.NOT, null, Filter.group(eq("a", 1)))
// after
new Filter.Expression(Filter.ExpressionType.NOT, Filter.group(eq("a", 1)), null)
Defensive patterns

Strategy: validation

Validate before calling

static void requireLeft(Filter.Expression e) { if (e.left() == null) throw new IllegalStateException("Expression " + e.type() + " needs a left operand"); }

Type guard

static boolean hasLeft(Filter.Expression e) { return e.left() != null; }

Try / catch

try { store.similaritySearch(req); } catch (IllegalArgumentException e) { if (e.getMessage().endsWith("requires a left operand")) { /* fix NOT operand placement */ } }

Prevention

When it happens

Trigger: Constructing a Filter.Expression with a null left operand — most typically a NOT expression built as new Filter.Expression(NOT, null, child) — then evaluating it via S3VectorStore similaritySearch post-filtering.

Common situations: NOT misuse: some filter models put the negated child on the right, but this evaluator reads NOT's child from the left; manual expression construction; migrating filters from stores where NOT carries its operand on the right.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


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