pentaho/pentaho-kettle · error · KettleValueException

The 'A-B%' function only works on numeric data

Error message

The 'A-B%' function only works on numeric data

What it means

The 'A-B%' operation computes A - (A*B/100) and is implemented only for numeric types (Number, Integer, BigNumber). A non-numeric operand falls through to the default branch and throws this KettleValueException. The variable-space-less MathContext-null path is the one throwing here.

Solutions

  1. Convert the non-numeric field to Number/Integer (Select values step) before the 'A-B%' calculation
  2. Fix the input step's field type so percentages are parsed as numeric at read time
  3. Check the Calculator step's field A/B mappings for the 'A-B%' line
  4. Prefer BigNumber for monetary A values to also avoid rounding surprises

Example fix

// before
ValueMetaInterface pctMeta = new ValueMetaString("discount_pct");
ValueDataUtil.percent2(numMeta, dataA, pctMeta, dataB); // throws
// after
ValueMetaInterface pctMeta = new ValueMetaNumber("discount_pct");
Object pct = ValueDataUtil.convertData(new ValueMetaString("discount_pct"), dataB, pctMeta);
ValueDataUtil.percent2(numMeta, dataA, pctMeta, pct);
Defensive patterns

Strategy: type-guard

Validate before calling

if (isNumericMeta(metaA) && isNumericMeta(metaB)) {
  result = /* 'A-B%' call */;
} else {
  ValueMetaInterface np = new ValueMetaNumber(metaB != null ? metaB.getName() : "b");
  Object p = ValueDataUtil.convertData(metaB, dataB, np);
  // call with numeric metas
}

Type guard

boolean isNumericMeta(ValueMetaInterface meta) {
  if (meta == null) return false;
  int t = meta.getType();
  return t == ValueMetaInterface.TYPE_NUMBER
    || t == ValueMetaInterface.TYPE_INTEGER
    || t == ValueMetaInterface.TYPE_BIGNUMBER;
}

Try / catch

try {
  result = percentFn(metaA, dataA, metaB, dataB);
} catch (KettleValueException e) {
  if (e.getMessage().contains("A-B%")) {
    logError("Non-numeric operand for 'A-B%': " + (metaB != null ? metaB.getName() + "=" + metaB.getTypeDesc() : "?"));
  }
  throw e;
}

Prevention

When it happens

Trigger: ValueDataUtil.percent2-style 'A-B%' calculation where metaA or metaB type is String/Date/Boolean, e.g. a discount-percent field read as text.

Common situations: Discount or tax percentage columns imported as strings from Excel/CSV, JSON input fields typed as string, mapping mistakes in the Calculator step after schema changes.

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 pentaho/pentaho-kettle@f3058517a1 (2026-09-13). Data as JSON: /api/errors/2b88680de20564b6. Report an issue: GitHub.

Appendix: source

Thrown at core/src/main/java/org/pentaho/di/core/row/ValueDataUtil.java:1035

  public static Object percent2( ValueMetaInterface metaA, Object dataA, ValueMetaInterface metaB, Object dataB ) throws KettleValueException {
    if ( dataA == null || dataB == null ) {
      return null;
    }

    switch ( metaA.getType() ) {
      case ValueMetaInterface.TYPE_NUMBER:
        return new Double( metaA.getNumber( dataA ).doubleValue()
          - divideDoubles( multiplyDoubles( metaA.getNumber( dataA ), metaB.getNumber( dataB ) ), 100.0D ) );
      case ValueMetaInterface.TYPE_INTEGER:
        return new Long( metaA.getInteger( dataA ).longValue()
          - divideLongs( multiplyLongs( metaA.getInteger( dataA ), metaB.getInteger( dataB ) ), 100L ) );
      case ValueMetaInterface.TYPE_BIGNUMBER:
        return metaA.getBigNumber( dataA ).subtract(
          divideBigDecimals( multiplyBigDecimals(
            metaB.getBigNumber( dataB ), metaA.getBigNumber( dataA ), null ), new BigDecimal( 100 ),
            (MathContext) null ) );
      default:
        throw new KettleValueException( "The 'A-B%' function only works on numeric data" );
    }
  }

  public static Object percent2( ValueMetaInterface metaA, Object dataA, ValueMetaInterface metaB, Object dataB, VariableSpace space ) throws KettleValueException {
    if ( dataA == null || dataB == null ) {
      return null;
    }

    switch ( metaA.getType() ) {
      case ValueMetaInterface.TYPE_NUMBER:
        return new Double( metaA.getNumber( dataA ).doubleValue()
          - divideDoubles( multiplyDoubles( metaA.getNumber( dataA ), metaB.getNumber( dataB ) ), 100.0D ) );
      case ValueMetaInterface.TYPE_INTEGER:
        return new Long( metaA.getInteger( dataA ).longValue()
          - divideLongs( multiplyLongs( metaA.getInteger( dataA ), metaB.getInteger( dataB ) ), 100L ) );
      case ValueMetaInterface.TYPE_BIGNUMBER:
        return metaA.getBigNumber( dataA ).subtract(
          divideBigDecimals( multiplyBigDecimals(

View on GitHub (pinned to f3058517a1)