spring-projects/spring-ai · error · IllegalStateException

The 'IN' operator should have been transformed into chain…

Error message

The 'IN' operator should have been transformed into chain of OR/EQ expressions.

What it means

The Weaviate converter cannot emit IN directly: before conversion, IN expressions are (or must be) transformed into a chain of OR/EQ expressions, because Weaviate's GraphQL where filter has no native IN operator. Encountering IN in getOperationSymbol means the pre-transformation step didn't run, so an IllegalStateException is thrown.

Solutions

  1. Let the standard conversion path (convert/doExpression) handle the expression — it expands IN into OR of EQ automatically.
  2. Manually rewrite your filter as OR of EQ expressions: (a==1 OR a==2 OR a==3).
  3. If you override doExpression in a subclass, ensure you still expand IN/NIN before delegating to getOperationSymbol.

Example fix

// before
var expr = new Filter.Expression(IN, new Key("color"), new Value(List.of("red", "blue"))); // passed straight to operand conversion
// after
var or1 = new Filter.Expression(EQ, new Key("color"), new Value("red"));
var or2 = new Filter.Expression(EQ, new Key("color"), new Value("blue"));
var expr = new Filter.Expression(OR, or1, or2);
Defensive patterns

Strategy: type-guard

Validate before calling

if (expr.type() == Filter.ExpressionType.IN) { throw new IllegalArgumentException("Use OR/EQ chains or the standard conversion path for IN"); }

Type guard

static boolean requiresExpansion(Filter.Expression e) { return e.type() == Filter.ExpressionType.IN || e.type() == Filter.ExpressionType.NIN; }

Try / catch

try { String q = converter.convertExpression(expr); } catch (IllegalStateException e) { if (e.getMessage().contains("'IN' operator")) { /* expand IN into OR/EQ yourself */ } else throw e; }

Prevention

When it happens

Trigger: Calling the converter with a Filter.Expression of type IN that bypassed the standard doExpression transformation, e.g. invoking getOperationSymbol directly or a code path that skips the IN-expansion preprocessing.

Common situations: Custom code calling the converter internals directly; a subclass overriding doExpression and removing the IN expansion; version mismatch where the transformation was expected upstream.

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/824809862cd158be. Report an issue: GitHub.

Appendix: source

Thrown at vector-stores/spring-ai-weaviate-store/src/main/java/org/springframework/ai/vectorstore/weaviate/WeaviateFilterExpressionConverter.java:118

		}
		else {
			this.convertOperand(exp.left(), context);
			context.append(getOperationSymbol(exp));
			this.convertOperand(exp.right(), context);
		}
	}

	private String getOperationSymbol(Expression exp) {
		return switch (exp.type()) {
			case AND -> "operator:And \n";
			case OR -> "operator:Or \n";
			case EQ -> "operator:Equal \n";
			case NE -> "operator:NotEqual \n";
			case LT -> "operator:LessThan \n";
			case LTE -> "operator:LessThanEqual \n";
			case GT -> "operator:GreaterThan \n";
			case GTE -> "operator:GreaterThanEqual \n";
			case IN -> throw new IllegalStateException(
					"The 'IN' operator should have been transformed into chain of OR/EQ expressions.");
			case NIN -> throw new IllegalStateException(
					"The 'NIN' operator should have been transformed into chain of AND/NEQ expressions.");
			default -> throw new UnsupportedOperationException("Not supported expression type:" + exp.type());
		};
	}

	@Override
	protected void doKey(Key key, StringBuilder context) {
		var identifier = (hasOuterQuotes(key.key())) ? removeOuterQuotes(key.key()) : key.key();
		context.append("path:[");
		emitJsonValue(withMetaPrefix(identifier), context);
		context.append("] \n");
	}

	public String withMetaPrefix(String identifier) {
		if (SYSTEM_IDENTIFIERS.contains(identifier)) {
			return identifier;

View on GitHub (pinned to 98a7beda4f)