spring-projects/spring-ai · error · UnsupportedOperationException

Unsupported operator:

Error message

Unsupported operator: 

What it means

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.

Source

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

			case LTE:
			case LT:
				return Document.fromMap(Map.of(((Filter.Key) expression.left()).key(), Document
					.fromMap(Map.of(operationType, wrapValue(Objects.requireNonNull(expression.right()))))));

			case IN:
			case NIN:
				Document document = wrapValue(Objects.requireNonNull(expression.right()));
				return Document.fromMap(Map.of(((Filter.Key) expression.left()).key(),
						Document.fromMap(Map.of(operationType, document))));

			case AND:
			case OR:
				Document leftDocument = wrapValue(Objects.requireNonNull(expression.left()));
				Document rightDocument = wrapValue(Objects.requireNonNull(expression.right()));
				return Document.fromMap(Map.of(operationType, Document.fromList(List.of(leftDocument, rightDocument))));

			default:
				throw new UnsupportedOperationException("Unsupported operator: " + expression.type());
		}
	}

	private Document wrapValue(Filter.Operand operand) {
		if (operand instanceof Filter.Value) {
			return convertToDocument(((Filter.Value) operand).value());
		}
		else if (operand instanceof Filter.Key) {
			return Document.fromString(((Filter.Key) operand).key());
		}
		else if (operand instanceof Filter.Group) {
			Filter.Expression expression = ((Filter.Group) operand).content();
			return convertExpression(expression);
		}
		else {
			return convertExpression((Filter.Expression) operand);
		}
	}

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. 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.
  2. Negate in Java instead: run the search with the positive predicate and filter out results client-side.
  3. Upgrade Spring AI in case a newer version adds a NOT case to convertExpression.
  4. Wrap the converter or contribute a NOT branch that wraps the child expression in a {"$not": ...} Document.

Example fix

// before
new Filter.Expression(Filter.ExpressionType.NOT, Filter.group(eq("category", "x")), null)
// after
new Filter.Expression(Filter.ExpressionType.NE, new Filter.Key("category"), new Filter.Value("x"))
Defensive patterns

Strategy: validation

Validate before calling

private static void assertNoNot(Filter.Expression e) {
  if (e.type() == Filter.ExpressionType.NOT) throw new IllegalArgumentException("NOT not supported by S3 converter");
  if (e.left() instanceof Filter.Expression l) assertNoNot(l);
  if (e.right() instanceof Filter.Expression r) assertNoNot(r);
}

Type guard

static boolean isRewritable(Filter.ExpressionType t) { return t != Filter.ExpressionType.NOT; }

Try / catch

try { store.similaritySearch(req); } catch (UnsupportedOperationException e) { /* fall back to unfiltered search + client-side NOT filter */ }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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