apache/dubbo · error · ClassCastException

Number expected to be long: ${d}

Error message

Number expected to be long: ${d}

What it means

Thrown by AbstractJsonUtilImpl.getNumberAsLong(Map,String) when obj contains key, the value is a Double, but the long cast loses precision — i.e. d.longValue() != d. This guards against silently truncating a fractional Double (e.g. 3.5) into a long. It is a ClassCastException naming the offending Double. Only the Double branch reaches it; String values go through Long.parseLong.

Source

Thrown at dubbo-common/src/main/java/org/apache/dubbo/common/json/impl/AbstractJsonUtilImpl.java:185

    /**
     * Gets a number from an object for the given key, casted to an long.  If the key is not
     * present, this returns null.  If the value does not represent a long integer, throws an
     * exception.
     */
    @Override
    public Long getNumberAsLong(Map<String, ?> obj, String key) {
        assert obj != null;
        assert key != null;
        if (!obj.containsKey(key)) {
            return null;
        }
        Object value = obj.get(key);
        if (value instanceof Double) {
            Double d = (Double) value;
            long l = d.longValue();
            if (l != d) {
                throw new ClassCastException("Number expected to be long: " + d);
            }
            return l;
        }
        if (value instanceof String) {
            try {
                return Long.parseLong((String) value);
            } catch (NumberFormatException e) {
                throw new IllegalArgumentException(
                        String.format("value '%s' for key '%s' is not a long integer", value, key));
            }
        }
        throw new IllegalArgumentException(String.format("value '%s' for key '%s' is not a long integer", value, key));
    }

    /**
     * Gets a string from an object for the given key.  If the key is not present, this returns null.
     * If the value is not a String, throws an exception.
     */

View on GitHub (pinned to 3a3043227f)

Solutions

  1. If truncation/rounding is acceptable, pre-round: Math.round(d) before reading.
  2. If the fractional value is a data error, fix the producer to emit whole numbers.
  3. Switch to getNumberAsDouble if the field is genuinely non-integral.
  4. Catch ClassCastException and treat as a validation error.

Example fix

// before
Long id = jsonUtil.getNumberAsLong(obj, "id"); // id = 42.7
// after - round then read
Object raw = obj.get("id");
Long id = raw instanceof Double ? Math.round((Double) raw) : jsonUtil.getNumberAsLong(obj, "id");
Defensive patterns

Strategy: type-guard

Validate before calling

Object raw = obj.get(key);
if (raw instanceof Double) {
    double d = (Double) raw;
    if (d != Math.rint(d)) {
        return Math.round(d); // round before reading as long
    }
}
return jsonUtil.getNumberAsLong(obj, key);

Type guard

private static boolean isWholeDouble(Object value) {
    if (!(value instanceof Double)) return value instanceof String;
    double d = (Double) value;
    return d == Math.rint(d);
}

Try / catch

try {
    return jsonUtil.getNumberAsLong(obj, key);
} catch (ClassCastException e) {
    Object raw = obj.get(key);
    if (raw instanceof Double) return Math.round((Double) raw); // recover
    throw e;
}

Prevention

When it happens

Trigger: jsonUtil.getNumberAsLong(obj, key) where obj.get(key) is a Double with a non-zero fractional part (e.g. 1.5, 99.9). Whole-valued doubles pass; anything off-integer throws.

Common situations: A field that is usually a count (integral) but occasionally a ratio/average; floating accumulation error turning 5 into 5.0000001; a Double-typed upstream widened from a fractional source; an ID/timestamp mistakenly encoded with a decimal portion.

Related errors


AI-assisted analysis of apache/dubbo@3a3043227f (2026-08-14). Data as JSON: /api/errors/c9204348a6f4617a. Report an issue: GitHub.