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 TYPE_NUMBER, TYPE_INTEGER, and TYPE_BIGNUMBER. A non-numeric operand falls to the default branch and throws this KettleValueException (MathContext-null path).

Solutions

  1. Convert the string field to Number/Integer/BigNumber before the calculation (Select values step)
  2. Fix the originating input step's column type mapping
  3. Verify the Calculator 'A+B%' field selections reference numeric columns
  4. Filter or default non-numeric rows before the step to avoid transformation aborts

Example fix

// before
ValueDataUtil.percent3(numMeta, dataA, strMeta, dataB); // throws
// after
ValueMetaInterface numB = new ValueMetaNumber("b");
Object b = ValueDataUtil.convertData(strMeta, dataB, numB);
ValueDataUtil.percent3(numMeta, dataA, numB, b);
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: percent3-style 'A+B%' calculation without VariableSpace where either operand's value type is not numeric (String, Date, Boolean).

Common situations: Markup/growth-rate percentage fields stored as text from spreadsheets, API/JSON inputs mapped as strings, wrong field chosen in the Calculator step.

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/c40a27f2226da740. Report an issue: GitHub.

Appendix: source

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

  public static Object percent3( 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 ).add(
          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 percent3( 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 ).add(
          divideBigDecimals( multiplyBigDecimals(

View on GitHub (pinned to f3058517a1)