spring-projects/spring-ai · error · RuntimeException
Unsupported expression type
Error message
Unsupported expression type %s. Only NOT is supported here
What it means
Neo4jVectorFilterExpressionConverter.doNot is only designed to render Filter.Expression nodes of type NOT; any other type passed here indicates a malformed expression tree and triggers this RuntimeException. It is an internal invariant check ('should not happen, but better safe than sorry') inside the filter-to-Cypher conversion.
Solutions
- Build expressions via the Filter builder API (Filter.eq(), Filter.not(), etc.) instead of raw constructors so the tree stays consistent.
- Verify the expression passed to the parser has type NOT before expecting doNot handling.
- Check for custom code that rewires Filter.Expression nodes and fix the type/operand assignment.
Example fix
// before new Filter.Expression(Filter.ExpressionType.AND, left, right) mistakenly routed to doNot // after new Filter.Expression(Filter.ExpressionType.NOT, inner, null); // only NOT reaches doNot
Defensive patterns
Strategy: validation
Validate before calling
static void assertNotTyped(Filter.Expression exp) {
if (exp.type() != Filter.ExpressionType.NOT) {
throw new IllegalArgumentException("doNot requires NOT expression");
}
} Type guard
boolean isNotExpression(Filter.Expression exp) {
return exp != null && exp.type() == Filter.ExpressionType.NOT;
} Try / catch
try {
vectorStore.similaritySearch(request);
} catch (RuntimeException e) {
if (e.getMessage().contains("Only NOT is supported here")) {
throw new IllegalArgumentException("Malformed filter expression tree", e);
}
} Prevention
- Construct filters with the Filter builder API to keep the AST consistent
- Never hand-build Filter.Expression nodes with mismatched type/left operands
- Unit-test custom filter trees before deploying
When it happens
Trigger: A Filter.Expression with type != NOT is dispatched to doNot during conversion — only possible with a hand-constructed or corrupted Filter.Expression tree, or a custom FilterExpressionConverter misuse.
Common situations: Manually building Filter.Expression objects with wrong type/left operands; custom subclasses of FilterExpressionTextParser calling doExpression on invalid nodes; bugs after refactoring filter ASTs.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- Not supported expression type:
- Can not negate operand of type:
- Cannot compare values of incompatible types
- Cannot compare values of incompatible types
- Cannot compare values of types
AI-assisted analysis of spring-projects/spring-ai@98a7beda4f (2026-09-11).
Data as JSON: /api/errors/585a3e54571ba164.
Report an issue: GitHub.
Appendix: source
Thrown at vector-stores/spring-ai-neo4j-store/src/main/java/org/springframework/ai/vectorstore/neo4j/filter/Neo4jVectorFilterExpressionConverter.java:73
case EQ -> " = ";
case NE -> " <> ";
case LT -> " < ";
case LTE -> " <= ";
case GT -> " > ";
case GTE -> " >= ";
case IN -> " IN ";
case NOT, NIN -> " NOT ";
// you never know what the future might bring
default -> throw new RuntimeException("Not supported expression type: " + exp.type());
};
}
@Override
protected void doNot(Expression expression, StringBuilder context) {
Filter.ExpressionType expressionType = expression.type();
// should not happen, but better safe than sorry
if (expressionType != Filter.ExpressionType.NOT) {
throw new RuntimeException(
"Unsupported expression type %s. Only NOT is supported here".formatted(expressionType));
}
// explicitly prefix the embedded expression with NOT
context.append("NOT ").append(this.convertOperand(expression.left()));
}
@Override
protected void doKey(Key key, StringBuilder context) {
String sanitized = SchemaNames.sanitize("metadata." + key.key(), true)
.orElseThrow(() -> new IllegalArgumentException(
"Invalid or empty metadata key cannot be used in a Neo4j filter expression: '%s'"
.formatted(key.key())));
context.append("node.").append(sanitized);
}
@Override
protected void doStartGroup(Group group, StringBuilder context) {View on GitHub (pinned to 98a7beda4f)