{"record":{"id":"3b035492e13ac76d","repo":"spring-projects/spring-ai","slug":"unsupported-operator","errorCode":null,"errorMessage":"Unsupported operator: ","messagePattern":"Unsupported operator: ","errorType":"exception","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"vector-stores/spring-ai-s3-vector-store/src/main/java/org/springframework/ai/vectorstore/s3/S3VectorFilterSearchExpressionConverter.java","lineNumber":89,"sourceCode":"\t\t\tcase LTE:\n\t\t\tcase LT:\n\t\t\t\treturn Document.fromMap(Map.of(((Filter.Key) expression.left()).key(), Document\n\t\t\t\t\t.fromMap(Map.of(operationType, wrapValue(Objects.requireNonNull(expression.right()))))));\n\n\t\t\tcase IN:\n\t\t\tcase NIN:\n\t\t\t\tDocument document = wrapValue(Objects.requireNonNull(expression.right()));\n\t\t\t\treturn Document.fromMap(Map.of(((Filter.Key) expression.left()).key(),\n\t\t\t\t\t\tDocument.fromMap(Map.of(operationType, document))));\n\n\t\t\tcase AND:\n\t\t\tcase OR:\n\t\t\t\tDocument leftDocument = wrapValue(Objects.requireNonNull(expression.left()));\n\t\t\t\tDocument rightDocument = wrapValue(Objects.requireNonNull(expression.right()));\n\t\t\t\treturn Document.fromMap(Map.of(operationType, Document.fromList(List.of(leftDocument, rightDocument))));\n\n\t\t\tdefault:\n\t\t\t\tthrow new UnsupportedOperationException(\"Unsupported operator: \" + expression.type());\n\t\t}\n\t}\n\n\tprivate Document wrapValue(Filter.Operand operand) {\n\t\tif (operand instanceof Filter.Value) {\n\t\t\treturn convertToDocument(((Filter.Value) operand).value());\n\t\t}\n\t\telse if (operand instanceof Filter.Key) {\n\t\t\treturn Document.fromString(((Filter.Key) operand).key());\n\t\t}\n\t\telse if (operand instanceof Filter.Group) {\n\t\t\tFilter.Expression expression = ((Filter.Group) operand).content();\n\t\t\treturn convertExpression(expression);\n\t\t}\n\t\telse {\n\t\t\treturn convertExpression((Filter.Expression) operand);\n\t\t}\n\t}","sourceCodeStart":71,"sourceCodeEnd":107,"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/S3VectorFilterSearchExpressionConverter.java#L71-L107","documentation":"convertExpression throws UnsupportedOperationException when the top-level expression type is not one of EQ/NE/GTE/GT/LTE/LT, IN/NIN, or AND/OR. In practice this fires for NOT (and any other unmapped type): NOT has a symbol in getOperationSymbol but no case in convertExpression's switch, so it reaches the default branch.","triggerScenarios":"Passing a filter containing a NOT expression (e.g. new Filter.Expression(NOT, group, null)) to S3VectorStore.similaritySearch; this always throws because convertExpression has no NOT case, despite getOperationSymbol mapping it to $not.","commonSituations":"Building filters like NOT(category = 'x') against the S3 vector store; migrating queries from other Spring AI vector stores where NOT works; library gap where NOT is half-supported.","solutions":["Avoid NOT at the top level or inside filters; rewrite using De Morgan's law, e.g. NOT(A AND B) -> (!A) OR (!B) expressed with NE/NIN where possible.","Negate in Java instead: run the search with the positive predicate and filter out results client-side.","Upgrade Spring AI in case a newer version adds a NOT case to convertExpression.","Wrap the converter or contribute a NOT branch that wraps the child expression in a {\"$not\": ...} Document."],"exampleFix":"// before\nnew Filter.Expression(Filter.ExpressionType.NOT, Filter.group(eq(\"category\", \"x\")), null)\n// after\nnew Filter.Expression(Filter.ExpressionType.NE, new Filter.Key(\"category\"), new Filter.Value(\"x\"))","handlingStrategy":"validation","validationCode":"private static void assertNoNot(Filter.Expression e) {\n  if (e.type() == Filter.ExpressionType.NOT) throw new IllegalArgumentException(\"NOT not supported by S3 converter\");\n  if (e.left() instanceof Filter.Expression l) assertNoNot(l);\n  if (e.right() instanceof Filter.Expression r) assertNoNot(r);\n}","typeGuard":"static boolean isRewritable(Filter.ExpressionType t) { return t != Filter.ExpressionType.NOT; }","tryCatchPattern":"try { store.similaritySearch(req); } catch (UnsupportedOperationException e) { /* fall back to unfiltered search + client-side NOT filter */ }","preventionTips":["Never emit NOT at any level of filters sent to S3VectorStore; rewrite with NE/NIN.","Negate results in application code rather than in the filter expression.","Track converter switch coverage when new Spring AI filter operators are introduced."],"tags":["vector-store","filter-expression","unsupported-operator","not-operator","aws-s3-vectors"],"backgroundTag":"unsupported-operation","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"}