{"record":{"id":"83821475aba3176c","repo":"spring-projects/spring-ai","slug":"expected-a-key-operand-but-got-838214","errorCode":null,"errorMessage":"Expected a Key operand but got: ","messagePattern":"Expected a Key operand but got: ","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"vector-stores/spring-ai-s3-vector-store/src/main/java/org/springframework/ai/vectorstore/s3/S3VectorStoreFilterExpressionEvaluator.java","lineNumber":108,"sourceCode":"\tprivate Filter.Operand right(Filter.Expression expression) {\n\t\tFilter.Operand right = expression.right();\n\t\tif (right == null) {\n\t\t\tthrow new IllegalArgumentException(\n\t\t\t\t\t\"Expression of type %s requires a right operand\".formatted(expression.type()));\n\t\t}\n\t\treturn right;\n\t}\n\n\tprivate @Nullable Object metadataValue(Filter.Operand operand, Map<String, Object> metadata) {\n\t\tif (operand instanceof Filter.Key key) {\n\t\t\tString k = key.key();\n\t\t\tif (k.length() >= 2\n\t\t\t\t\t&& ((k.startsWith(\"\\\"\") && k.endsWith(\"\\\"\")) || (k.startsWith(\"'\") && k.endsWith(\"'\")))) {\n\t\t\t\tk = k.substring(1, k.length() - 1);\n\t\t\t}\n\t\t\treturn metadata.get(k);\n\t\t}\n\t\tthrow new IllegalArgumentException(\"Expected a Key operand but got: \" + operand.getClass().getName());\n\t}\n\n\tprivate Object filterValue(Filter.Operand operand) {\n\t\tif (operand instanceof Filter.Value filterValue) {\n\t\t\tObject value = filterValue.value();\n\t\t\treturn (value instanceof Date date) ? DATE_FORMATTER.format(date.toInstant()) : value;\n\t\t}\n\t\tthrow new IllegalArgumentException(\"Expected a Value operand but got: \" + operand.getClass().getName());\n\t}\n\n\tprivate int compare(@Nullable Object metaVal, @Nullable Object filterVal) {\n\t\tif (metaVal == null && filterVal == null) {\n\t\t\treturn 0;\n\t\t}\n\t\tif (metaVal == null) {\n\t\t\treturn -1;\n\t\t}\n\t\tif (filterVal == null) {","sourceCodeStart":90,"sourceCodeEnd":126,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/vector-stores/spring-ai-s3-vector-store/src/main/java/org/springframework/ai/vectorstore/s3/S3VectorStoreFilterExpressionEvaluator.java#L90-L126","documentation":"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.","triggerScenarios":"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.","commonSituations":"Reversed comparisons like 5 > age written as filters; filters copied from textual DSLs that permit either side; code generators that place operands inconsistently.","solutions":["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).","Use Filter.expr(\"field == value\") string parsing, which always produces key-left form.","Add a sanity check that left() instanceof Filter.Key before submitting the filter.","If the right side is the Key and left is the Value, swap operands and swap EQ<->NE or LT<->GT etc."],"exampleFix":"// before\nnew Filter.Expression(Filter.ExpressionType.GT, new Filter.Value(5), new Filter.Key(\"age\"))\n// after\nnew Filter.Expression(Filter.ExpressionType.LT, new Filter.Key(\"age\"), new Filter.Value(5))","handlingStrategy":"validation","validationCode":"static void requireKeyLeft(Filter.Expression e) {\n  if (List.of(EQ, NE, GT, GTE, LT, LTE, IN, NIN, ISNULL, ISNOTNULL).contains(e.type())\n      && !(e.left() instanceof Filter.Key)) throw new IllegalStateException(\"Left side must be a metadata Key\");\n}","typeGuard":"static boolean isKeyLeft(Filter.Expression e) { return e.left() instanceof Filter.Key; }","tryCatchPattern":"try { store.similaritySearch(req); } catch (IllegalArgumentException e) { if (e.getMessage().startsWith(\"Expected a Key operand\")) { /* swap operands and invert operator */ } }","preventionTips":["Always write filters as field OP literal, never literal OP field.","Use Filter.expr(...) which normalizes to key-left form.","Add a lint check that rejects Value-on-left comparisons."],"tags":["vector-store","filter-expression","operand-order","post-filtering","aws-s3-vectors"],"backgroundTag":"invalid-argument-value","analyzedSha":"98a7beda4f29d80a71c5837eb4053b03a93a46f7","analyzedAt":"2026-09-11T14:15:49.441Z","contentChangedAt":"2026-09-11T14:15:49.441Z","schemaVersion":2},"datasetVersion":"2026-09-14T11:17:12.474Z"}