elastic/elasticsearch · error · IllegalArgumentException

${fullVal} cannot be converted to ${clazz.getSimpleName()} w

Error message

${fullVal} cannot be converted to ${clazz.getSimpleName()} without data loss

What it means

Thrown by AbstractXContentParser.ensureNumberConversion() when coerce is false and a narrowing conversion from double to the target integer type would lose fractional data. The method compares the double value (fullVal) with the already-narrowed result; if they differ, the original value had a fractional part that would be silently truncated.

Source

Thrown at libs/x-content/src/main/java/org/elasticsearch/xcontent/support/AbstractXContentParser.java:81

        this.restApiVersion = restApiVersion;
    }

    public AbstractXContentParser(NamedXContentRegistry xContentRegistry, DeprecationHandler deprecationHandler) {
        this(xContentRegistry, deprecationHandler, RestApiVersion.current());
    }

    // The 3rd party parsers we rely on are known to silently truncate fractions: see
    // http://fasterxml.github.io/jackson-core/javadoc/2.3.0/com/fasterxml/jackson/core/JsonParser.html#getShortValue()
    // If this behaviour is flagged as undesirable and any truncation occurs
    // then this method is called to trigger the"malformed" handling logic
    void ensureNumberConversion(boolean coerce, long result, Class<? extends Number> clazz) throws IOException {
        if (coerce == false) {
            double fullVal = doDoubleValue();
            if (result != fullVal) {
                // Need to throw type IllegalArgumentException as current catch
                // logic in NumberFieldMapper.parseCreateField relies on this
                // for "malformed" value detection
                throw new IllegalArgumentException(fullVal + " cannot be converted to " + clazz.getSimpleName() + " without data loss");
            }
        }
    }

    @Override
    public boolean isBooleanValue() throws IOException {
        return switch (currentToken()) {
            case VALUE_BOOLEAN -> true;
            case VALUE_STRING -> {
                if (hasTextCharacters()) {
                    yield Booleans.isBoolean(textCharacters(), textOffset(), textLength());
                } else {
                    yield Booleans.isBoolean(text());
                }
            }
            default -> false;
        };
    }

View on GitHub (pinned to db6a809a66)

Solutions

  1. Round or truncate the value before sending if integer storage is intended.
  2. Change the field mapping type from integer/long to float/double if fractional values are expected.
  3. Enable coerce in the mapping to accept silent truncation of fractional parts.
  4. Validate numeric values client-side to ensure they have no fractional component for integer fields.

Example fix

// before — fractional value into integer field with coerce=false
{ "quantity": 42.5 }

// after — integer value
{ "quantity": 42 }
Defensive patterns

Strategy: validation

Validate before calling

// Before indexing into integer fields, verify no fractional part if coerce is disabled
public static void validateNoFractionalLoss(Object value, String fieldName) {
    if (value instanceof Number n && n.doubleValue() != Math.floor(n.doubleValue())) {
        throw new IllegalArgumentException(
            fieldName + " has fractional part " + n + " that would be lost in integer conversion"
        );
    }
}

Try / catch

try {
    parser.intValue(false);
} catch (IllegalArgumentException e) {
    if (e.getMessage().contains("cannot be converted to") && e.getMessage().contains("without data loss")) {
        throw new BadRequestException("Fractional value cannot be stored in integer field: " + e.getMessage());
    }
    throw e;
}

Prevention

When it happens

Trigger: A numeric field with coerce=false receiving a value like 42.5 for an integer or long field. The parser reads the value as a double internally, narrows it to long/int/short, and then detects that the double had a fractional part that was dropped. With coerce=true this truncation is allowed; with coerce=false it is rejected.

Common situations: Indexing a float/double value into an integer field mapping with coerce disabled. Client sending 3.14 where the mapping expects a long. Aggregation or script producing fractional results written to integer fields.

Related errors


AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12). Data as JSON: /api/errors/98013a410a6f54ea. Report an issue: GitHub.