OrchardCMS/OrchardCore · error · ArgumentException

Lower and upper bound range types don't match

Error message

Lower and upper bound range types don't match

What it means

RangeFilterProvider.CreateFilteredQuery supports range filters with a lower bound (gt/gte) and an upper bound (lt/lte). When both bounds are present their JSON value kinds must match (both numbers, or both strings, etc.); if they differ, e.g. gt is a number and lt is a string, the provider throws ArgumentException("Lower and upper bound range types don't match") because Lucene range queries need a consistent type for min and max.

Solutions

  1. Make both bounds the same JSON type: use {"gt": 10, "lt": 100} or {"gt": "a", "lt": "b"}, never a mix.
  2. If bounds are dates, quote both: {"gte": "2024-01-01", "lte": "2024-12-31"}.
  3. In dynamic query builders, coerce/serialize both bounds from the same source type before building the filter.
  4. Validate that gt/gte and lt/lte have equal JsonValueKind before calling CreateFilteredQuery.

Example fix

// before
{"range": {"Price": {"gt": 10, "lt": "100"}}}
// after
{"range": {"Price": {"gt": 10, "lt": 100}}}
Defensive patterns

Strategy: validation

Validate before calling

var obj = rangeValue.AsObject();
bool hasGt = obj.TryGetPropertyValue("gt", out var gt) || obj.TryGetPropertyValue("gte", out gt);
bool hasLt = obj.TryGetPropertyValue("lt", out var lt) || obj.TryGetPropertyValue("lte", out lt);
if (hasGt && hasLt && gt.ValueKind != lt.ValueKind)
{
    throw new InvalidOperationException(
        $"Range bounds must share the same JSON type (got {gt.ValueKind} vs {lt.ValueKind}).");
}

Type guard

static bool BoundsKindMatch(JsonElement? gt, JsonElement? lt) =>
    gt is null || lt is null || gt.GetValueKind() == lt.GetValueKind();

Try / catch

try
{
    var query = provider.CreateFilteredQuery(context);
}
catch (ArgumentException ex) when (ex.Message.Contains("types don't match"))
{
    _logger.LogError(ex, "Mixed range bound types: {Json}", filterJson);
    throw new QueryValidationException("Range lower/upper bounds must be the same type.", ex);
}

Prevention

When it happens

Trigger: Calling CreateFilteredQuery with {"range": {"Price": {"gt": 10, "lt": "100"}}} or any mix like {"gte": "2024-01-01", "lt": 5} where the lower and upper bound JSON kinds differ (number vs string, number vs boolean).

Common situations: Dates supplied as strings on one bound but parsed as numbers on the other; one bound quoted and the other not when hand-editing JSON; dynamic query builders formatting only one bound; data migrated between string-typed and numeric-typed fields.

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 OrchardCMS/OrchardCore@4306c0717f (2026-09-13). Data as JSON: /api/errors/3a5877c604ce1c37. Report an issue: GitHub.

Appendix: source

Thrown at src/OrchardCore/OrchardCore.Lucene.Core/QueryProviders/Filters/RangeFilterProvider.cs:68

                            break;
                        case "lt":
                            lt = element.Value;
                            nodeKind = lt.GetValueKind();
                            break;
                        case "lte":
                            lt = element.Value;
                            nodeKind = lt.GetValueKind();
                            includeUpper = true;
                            break;
                        case "boost":
                            boost = element.Value.Value<float>();
                            break;
                    }
                }

                if (gt is not null && lt is not null && gt.GetValueKind() != lt.GetValueKind())
                {
                    throw new ArgumentException("Lower and upper bound range types don't match");
                }

                switch (nodeKind)
                {
                    case JsonValueKind.Number:
                        if (gt.AsValue().TryGetValue<long>(out var minInt) &&
                            lt.AsValue().TryGetValue<long>(out var maxInt))
                        {
                            rangeQuery = NumericRangeQuery.NewInt64Range(field, minInt, maxInt, includeLower, includeUpper);
                        }
                        else if (gt.AsValue().TryGetValue<double>(out var minFloat) &&
                            lt.AsValue().TryGetValue<double>(out var maxFloat))
                        {
                            rangeQuery = NumericRangeQuery.NewDoubleRange(field, minFloat, maxFloat, includeLower, includeUpper);
                        }
                        else
                        {
                            throw new ArgumentException($"Unsupported range value type: {type}");

View on GitHub (pinned to 4306c0717f)