{"record":{"id":"535f3b237c3478de","repo":"spring-projects/spring-ai","slug":"not-supported-expression-type","errorCode":null,"errorMessage":"Not supported expression type: ","messagePattern":"Not supported expression type: ","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"vector-stores/spring-ai-azure-store/src/main/java/org/springframework/ai/vectorstore/azure/AzureAiSearchFilterExpressionConverter.java","lineNumber":90,"sourceCode":"\t}\n\n\tprotected void doEndValueRange(Filter.Value listValue, StringBuilder context) {\n\t\tcontext.append(\"'\");\n\t}\n\n\tprivate String getOperationSymbol(Expression exp) {\n\t\treturn switch (exp.type()) {\n\t\t\tcase AND -> \" and \";\n\t\t\tcase OR -> \" or \";\n\t\t\tcase EQ -> \" eq \";\n\t\t\tcase NE -> \" ne \";\n\t\t\tcase LT -> \" lt \";\n\t\t\tcase LTE -> \" le \";\n\t\t\tcase GT -> \" gt \";\n\t\t\tcase GTE -> \" ge \";\n\t\t\tcase IN -> \" search.in\";\n\t\t\tcase NIN -> \" not search.in\";\n\t\t\tdefault -> throw new RuntimeException(\"Not supported expression type: \" + exp.type());\n\t\t};\n\t}\n\n\t@Override\n\tpublic void doKey(Key key, StringBuilder context) {\n\t\tvar hasOuterQuotes = hasOuterQuotes(key.key());\n\t\tvar identifier = (hasOuterQuotes) ? removeOuterQuotes(key.key()) : key.key();\n\t\tvar prefixedIdentifier = withMetaPrefix(identifier);\n\t\tif (hasOuterQuotes) {\n\t\t\tprefixedIdentifier = \"'\" + prefixedIdentifier.trim() + \"'\";\n\t\t}\n\t\tcontext.append(prefixedIdentifier);\n\t}\n\n\t/**\n\t * Adds the metadata field prefix to the given identifier name. Azure AI Search\n\t * requires metadata fields to be prefixed with \"meta_\" to distinguish them from\n\t * system fields.","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/spring-projects/spring-ai/blob/98a7beda4f29d80a71c5837eb4053b03a93a46f7/vector-stores/spring-ai-azure-store/src/main/java/org/springframework/ai/vectorstore/azure/AzureAiSearchFilterExpressionConverter.java#L72-L108","documentation":"AzureAiSearchFilterExpressionConverter.getOperationSymbol translates Spring AI Filter.Expression types into Azure AI Search OData operator strings (lt, le, gt, ge, search.in). If the expression's type is not one of the six supported comparison types (e.g. AND, OR, NOT passed here unexpectedly), the default branch throws a RuntimeException naming the unsupported type. This is a defensive guard: group types should be routed elsewhere before reaching this switch.","triggerScenarios":"Calling the converter (directly or via AzureAiSearchVectorStore with similaritySearch(SearchRequest.builder().filterExpression(...))) with a Filter.Expression whose type() is not IN, NIN, LT, LTE, GT, or GTE — e.g. a raw Expression of type AND/OR/NOT that bypassed doExpression's normal grouping, or a newly added Filter.ExpressionType enum value in a newer Spring AI version not yet handled by this converter.","commonSituations":"Using a new Spring AI release that introduces new Filter.ExpressionType constants while the azure-store version is older; programmatically building Filter expressions and handing a group expression straight to getOperationSymbol instead of going through convertExpression/doExpression; custom subclasses calling getOperationSymbol directly.","solutions":["Only pass comparison-type expressions (LT, LTE, GT, GTE, IN, NIN) to this converter; wrap AND/OR in Filter.and()/Filter.or() and let doExpression route them to doStartGroup/doEndGroup","Upgrade spring-ai-azure-store and Spring AI core to matching versions so any new ExpressionType values are handled","Catch RuntimeException around filter conversion and simplify the expression tree to only supported operators","If you need an operator added, extend the switch in getOperationSymbol to map the missing type to its OData symbol"],"exampleFix":"// before\nExpression e = new Expression(ExpressionType.GT, key, value);\nString op = converter.getOperationSymbol(e); // ok only for comparisons\n// after\nFilter.ExpressionText text = new ExpressionText(\"year\");\nFilter.Expression e = GT(text, 2020); // build via Filter helper methods; convert whole tree with converter.convertExpression(expression)","handlingStrategy":"try-catch","validationCode":"private static final Set<ExpressionType> SUPPORTED = Set.of(ExpressionType.LT, ExpressionType.LTE, ExpressionType.GT, ExpressionType.GTE, ExpressionType.IN, ExpressionType.NIN);\nif (expr instanceof Expression e && !SUPPORTED.contains(e.type())) { throw new IllegalArgumentException(\"Azure converter only supports comparison types, got: \" + e.type()); }","typeGuard":"boolean isAzureComparable(Filter.Operand o) { return o instanceof Expression e && Set.of(ExpressionType.LT, ExpressionType.LTE, ExpressionType.GT, ExpressionType.GTE, ExpressionType.IN, ExpressionType.NIN).contains(e.type()); }","tryCatchPattern":"try { String filter = azureConverter.convertExpression(expression); ... } catch (RuntimeException e) { log.warn(\"Unsupported Azure filter expression: {}\", e.getMessage()); /* fallback to unfiltered or client-side filtering */ }","preventionTips":["Build filters only via the Filter helper DSL so group types are routed through the group handlers","Keep spring-ai-core and spring-ai-azure-store on aligned versions","Restrict user-supplied filter queries to comparison operators","Unit-test every ExpressionType your application emits against the converter"],"tags":["azure","filter-expression","odata","unsupported-operator"],"backgroundTag":"unsupported-enum-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"}