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

  1. Build expressions via the Filter builder API (Filter.eq(), Filter.not(), etc.) instead of raw constructors so the tree stays consistent.
  2. Verify the expression passed to the parser has type NOT before expecting doNot handling.
  3. 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

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


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)