{"record":{"id":"0f4a9fb1ba8d4f82","repo":"spring-projects/spring-ai","slug":"expected-a-value-operand-but-got","errorCode":null,"errorMessage":"Expected a Value operand but got: ","messagePattern":"Expected a Value operand but got: ","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"spring-ai-vector-store/src/main/java/org/springframework/ai/vectorstore/SimpleVectorStoreFilterExpressionEvaluator.java","lineNumber":168,"sourceCode":"\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\t/**\n\t * Extracts the constant value from a {@link Filter.Value} operand. {@link Date}\n\t * instances are formatted to their ISO-8601 UTC string so they can be compared\n\t * directly with metadata strings stored in the same format.\n\t */\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\t/**\n\t * Compares two values. Numbers are promoted to {@code double} to allow cross-type\n\t * numeric comparison (e.g. {@code Integer} vs {@code Double}). All other\n\t * {@link Comparable} types are compared directly.\n\t *\n\t * <p>\n\t * Null ordering follows SQL {@code NULLS FIRST} semantics: {@code null} is considered\n\t * less than any non-null value. As a result, a missing metadata key causes ordered\n\t * comparisons ({@code GT}, {@code GTE}, {@code LT}, {@code LTE}) to behave as if the\n\t * key holds the smallest possible value — e.g. {@code year > 2020} returns\n\t * {@code false} when {@code year} is absent.\n\t */\n\t@SuppressWarnings(\"unchecked\")\n\tprivate int compare(@Nullable Object metaVal, @Nullable Object filterVal) {\n\t\tif (metaVal == null && filterVal == null) {\n\t\t\treturn 0;","sourceCodeStart":150,"sourceCodeEnd":186,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/spring-ai-vector-store/src/main/java/org/springframework/ai/vectorstore/SimpleVectorStoreFilterExpressionEvaluator.java#L150-L186","documentation":"The evaluator's filterValue() expects the right operand of a comparison to be a Filter.Value holding a constant. If it receives a Key (or another Operand type), it throws IllegalArgumentException with the operand's class name. The right side of a comparison must be a literal value, not a metadata key.","triggerScenarios":"A Filter.Expression like EQ(key, key) — two metadata keys compared — evaluated by SimpleVectorStore; nested Filter.Expression where a Value was expected.","commonSituations":"Hand-building expressions comparing two metadata fields; forgetting to wrap the constant in Filter.Value; translating SQL-style key-to-key comparisons.","solutions":["Wrap the constant in a Filter.Value on the right side of the expression.","Cross-field metadata comparison is unsupported — compute the comparison before building the filter.","Verify the parsed expression's right operand is a Filter.Value before search."],"exampleFix":"// before\nnew Filter.Expression(EQ, new Filter.Key(\"color\"), new Filter.Key(\"shade\"));\n// after\nnew Filter.Expression(EQ, new Filter.Key(\"color\"), new Filter.Value(\"shade\"));","handlingStrategy":"validation","validationCode":"boolean rightIsValue(Filter.Expression e) {\n    return e != null && e.right() instanceof Filter.Value;\n}","typeGuard":"Filter.Value asValue(Filter.Operand op) { return op instanceof Filter.Value v ? v : null; }","tryCatchPattern":"try {\n    vectorStore.similaritySearch(request);\n} catch (IllegalArgumentException ex) {\n    if (ex.getMessage().startsWith(\"Expected a Value operand\")) {\n        // wrap the right operand's constant in Filter.Value\n    } else throw ex;\n}","preventionTips":["Never compare two metadata keys; cross-field comparison is unsupported","Always construct constants as new Filter.Value(...)"],"tags":["vector-store","filter-expression","operand-type","spring-ai"],"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-14T05:17:10.506Z"}