spring-projects/spring-ai · error · IllegalArgumentException

Numeric value must be a Number

Error message

Numeric value must be a Number

What it means

In RedisFilterExpressionConverter.inclusive(), the boundary value of a numeric filter expression must be an instance of java.lang.Number. If value.value() is a String, Boolean or any other object, the converter throws this IllegalArgumentException because it cannot build a Redis NumericBoundary from it. This guards the numeric range construction for EQ/GTE/LTE-style boundaries.

Source

Thrown at vector-stores/spring-ai-redis-store/src/main/java/org/springframework/ai/vectorstore/redis/RedisFilterExpressionConverter.java:204

					MessageFormat.format("Tag operand {0} not supported", expression.type()));
		};
	}

	private Numeric numeric(Expression expression, Value value) {
		return switch (expression.type()) {
			case EQ -> new Numeric(inclusive(value), inclusive(value));
			case GT -> new Numeric(exclusive(value), NumericBoundary.POSITIVE_INFINITY);
			case GTE -> new Numeric(inclusive(value), NumericBoundary.POSITIVE_INFINITY);
			case LT -> new Numeric(NumericBoundary.NEGATIVE_INFINITY, exclusive(value));
			case LTE -> new Numeric(NumericBoundary.NEGATIVE_INFINITY, inclusive(value));
			default -> throw new UnsupportedOperationException(
					MessageFormat.format("Expression type {0} not supported for numeric fields", expression.type()));
		};
	}

	private NumericBoundary inclusive(Value value) {
		if (!(value.value() instanceof Number)) {
			throw new IllegalArgumentException("Numeric value must be a Number");
		}
		return new NumericBoundary(value.value(), false);
	}

	private NumericBoundary exclusive(Value value) {
		if (!(value.value() instanceof Number)) {
			throw new IllegalArgumentException("Numeric value must be a Number");
		}
		return new NumericBoundary(value.value(), true);
	}

	@Override
	protected void doSingleValue(Object value, StringBuilder context) {
		emitJsonValue(value, context);
	}

	record Numeric(NumericBoundary lower, NumericBoundary upper) {

View on GitHub (pinned to 98a7beda4f)

Solutions

  1. Pass a Java Number type (Integer, Long, Double, Float) as the filter value for numeric fields
  2. Parse String input explicitly before building the expression, e.g. Integer.parseInt(value)
  3. Verify the metadata field type at write time so numeric fields always hold numeric values

Example fix

// before
Filter.Expression expr = b.eq("year", "2024").build();
// after
Filter.Expression expr = b.eq("year", Integer.parseInt("2024")).build();
Defensive patterns

Strategy: type-guard

Validate before calling

if (!(rawValue instanceof Number)) throw new IllegalArgumentException("Numeric filter value must be a Number: " + rawValue);

Type guard

static boolean isNumericValue(Object v) { return v instanceof Number; }

Try / catch

try { store.similaritySearch(req); } catch (IllegalArgumentException e) { if (e.getMessage().contains("must be a Number")) { /* fix filter value type */ } }

Prevention

When it happens

Trigger: Passing a non-numeric value in a filter expression against a numeric field, e.g. eq("year", "2024") (String instead of int), or a metadata value that was stored/typed as a String but used in a numeric comparison.

Common situations: Reading filter values from JSON/HTTP request parameters where everything arrives as a String; metadata written with String values then used in numeric filters; auto-boxing assumptions after refactoring a field from numeric to text.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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