pentaho/pentaho-kettle · error · org.pentaho.di.core.exception.KettleValueException

The 'plus' function only works on numeric data and Strings.

Error message

The 'plus' function only works on numeric data and Strings.

What it means

ValueDataUtil.plus adds two values but only supports numeric types (Number, BigNumber, Date semantics) and String concatenation. If either ValueMeta's type falls outside the switch's supported cases, it throws KettleValueException. It is a type-compatibility guard for the Calculator/UDJE '+' operation.

Solutions

  1. Use a Select values / Calculator step to convert both fields to Number/Integer/String before '+'
  2. Fix the incoming field type metadata so it is numeric or String
  3. Check that no upstream step redefined the field type (e.g. Boolean)
  4. If concatenation was intended, ensure both metas are TYPE_STRING

Example fix

// before
ValueMetaInterface metaB = new ValueMetaBoolean("flag");
Object r = ValueDataUtil.plus(metaA, dataA, metaB, dataB); // throws
// after
ValueMetaInterface metaB = new ValueMetaInteger("flagAsInt");
Object b = metaB.convertData(new ValueMetaBoolean("flag"), dataB);
Object r = ValueDataUtil.plus(metaA, dataA, metaB, b);
Defensive patterns

Strategy: validation

Validate before calling

// Java: check both types are numeric or String before plus
int ta = metaA.getType(), tb = metaB.getType();
boolean ok = (ta == ValueMetaInterface.TYPE_NUMBER || ta == ValueMetaInterface.TYPE_INTEGER ||
              ta == ValueMetaInterface.TYPE_BIGNUMBER || ta == ValueMetaInterface.TYPE_STRING)
          && (tb == ValueMetaInterface.TYPE_NUMBER || tb == ValueMetaInterface.TYPE_INTEGER ||
              tb == ValueMetaInterface.TYPE_BIGNUMBER || tb == ValueMetaInterface.TYPE_STRING);
if (!ok) throw new IllegalArgumentException("Unsupported types for plus: " + ta + ", " + tb);

Type guard

boolean isNumericMeta(ValueMetaInterface m) {
  int t = m.getType();
  return t == ValueMetaInterface.TYPE_NUMBER || t == ValueMetaInterface.TYPE_INTEGER
      || t == ValueMetaInterface.TYPE_BIGNUMBER;
}

Try / catch

try {
  result = ValueDataUtil.plus(metaA, dataA, metaB, dataB);
} catch (KettleValueException e) {
  log.error("plus type mismatch: A=" + metaA.getTypeDesc() + " B=" + metaB.getTypeDesc());
  throw e;
}

Prevention

When it happens

Trigger: Calling ValueDataUtil.plus(metaA, dataA, metaB, dataB) where either value's meta type is not TYPE_NUMBER, TYPE_INTEGER, TYPE_BIGNUMBER, TYPE_STRING, or the other supported cases — e.g. Boolean, Binary, or an unknown/unspecified type.

Common situations: Calculator step '+' fed a Boolean or Binary field; incoming CSV field defaulted to 'Unknown' type; upstream step changed a field from Number to String/Boolean after a transform edit.

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

Appendix: source

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

        } else if ( valueA == null ) {
          return valueB;
        } else {
          return Boolean.valueOf( valueA.booleanValue() || valueB.booleanValue() );
        }
      }
      case ValueMetaInterface.TYPE_BIGNUMBER: {
        BigDecimal valueA = metaA.getBigNumber( dataA );
        BigDecimal valueB = metaB.getBigNumber( dataB );
        if ( valueB == null ) {
          return valueA;
        } else if ( valueA == null ) {
          return valueB;
        } else {
          return valueA.add( valueB );
        }
      }
      default:
        throw new KettleValueException( "The 'plus' function only works on numeric data and Strings." );
    }
  }

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

    switch ( metaA.getType() ) {
      case ValueMetaInterface.TYPE_STRING:
        return metaA.getString( dataA ) + metaB.getString( dataB ) + metaC.getString( dataC );
      case ValueMetaInterface.TYPE_NUMBER:
        return new Double( metaA.getNumber( dataA ).doubleValue()
          + metaB.getNumber( dataB ).doubleValue() + metaC.getNumber( dataC ).doubleValue() );
      case ValueMetaInterface.TYPE_INTEGER:
        return new Long( metaA.getInteger( dataA ).longValue()
          + metaB.getInteger( dataB ).longValue() + metaC.getInteger( dataC ).longValue() );

View on GitHub (pinned to f3058517a1)