apache/skywalking · error · IllegalArgumentException

Operator ' ' requires numeric operands; got non-numeric…

Error message

Operator '{opSymbol}' requires numeric operands; got non-numeric expression. Cast operands with 'as Integer/Long/Float/Double' to enable arithmetic.

What it means

Thrown at LAL compile time when a binary arithmetic operator other than + is applied to an operand the type inference does not consider numeric. Only + has a string-concatenation fallback; -, *, /, % require both sides to be statically numeric. Since parsed values from json{}/yaml{} are untyped Objects, the codegen cannot prove numericness and rejects the expression, suggesting an explicit 'as' cast.

Solutions

  1. Cast every operand with an explicit 'as' cast, e.g. (parsed.latency as Long) - (parsed.base as Long)
  2. Cast at least the accumulator-side of the chain so inference promotes correctly: parsed.latency as Long - 100
  3. For string concatenation use + (it falls back to concat), not other operators

Example fix

# before
timestamp parsed.ts - parsed.offset

# after
timestamp (parsed.ts as Long) - (parsed.offset as Long)
Defensive patterns

Strategy: validation

Prevention

When it happens

Trigger: parsed.latency - 100 where parsed.* is map-typed (Object); parsed.count * parsed.factor without casts; arithmetic on safe-navigated chains (parsed?.x / 2) whose static type is Object.

Common situations: Computing latency/duration deltas from parsed JSON fields; porting Groovy v1 rules that did dynamic numeric coercion at runtime; metric expressions in the extractor that mix strings and numbers.

Related errors


AI-assisted analysis of apache/skywalking@102af09b4a (2026-08-14). Data as JSON: /api/errors/938ad7f5685e7dd0. Report an issue: GitHub.

Appendix: source

Thrown at oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/v2/compiler/LALValueCodegen.java:1561

        String accExpr = accBuf.toString();

        for (int i = 1; i < parts.size(); i++) {
            final LALScriptModel.BinaryOp op = ops.get(i - 1);
            final ExprType rhsType = inferType(parts.get(i), genCtx);
            final StringBuilder rhsBuf = new StringBuilder();
            appendOperandRaw(rhsBuf, parts.get(i), rhsType, genCtx);
            final String rhsExpr = rhsBuf.toString();

            if (accType.isNumeric() && rhsType.isNumeric()) {
                final ExprType promoted = promote(accType, rhsType);
                final String widenedAcc = widenPrimitiveExpr(accExpr, accType, promoted);
                final String widenedRhs = widenPrimitiveExpr(rhsExpr, rhsType, promoted);
                accExpr = "(" + widenedAcc + " " + op.symbol() + " " + widenedRhs + ")";
                accType = promoted;
                continue;
            }
            if (op != LALScriptModel.BinaryOp.PLUS) {
                throw new IllegalArgumentException(
                    "Operator '" + op.symbol() + "' requires numeric operands; "
                        + "got non-numeric expression. Cast operands with "
                        + "'as Integer/Long/Float/Double' to enable arithmetic.");
            }
            // String concat path. Java's `+` only accepts the chain when
            // at least one operand is statically a String; otherwise (e.g.
            // `int + Object` or `Object + Object`) the source won't even
            // compile. Prepend a leading `""` whenever neither side is
            // statically String at the transition point. The numeric
            // arithmetic prefix is already in parens, so `("" + (1 + 2))`
            // still computes the sum first ("3"), preserving the
            // semantics of `1 + 2 + parsed.x` (= "3<obj>").
            if (accType != ExprType.STRING && rhsType != ExprType.STRING) {
                accExpr = "\"\" + " + accExpr;
            }
            accExpr = accExpr + " + " + rhsExpr;
            accType = ExprType.STRING;
        }

View on GitHub (pinned to 102af09b4a)