pentaho/pentaho-kettle · error · KettleValueException
The 'remainder' function only works on numeric data
Error message
The 'remainder' function only works on numeric data
What it means
The remainder helper computes A mod B (BigDecimal.remainder with UNLIMITED precision for BigNumber) and supports only Number, Integer, and BigNumber metadata types. A non-numeric operand type falls through to the default branch and throws this KettleValueException.
Solutions
- Convert both operands to numeric types before the Calculator step.
- Set field metadata to Number/Integer/BigNumber in a Select Values step.
- Check both metaA and metaB types in a UDJC step before computing the remainder.
Example fix
// before: fieldA % fieldB where both are String // after: Select Values: fieldA, fieldB -> Integer; then remainder(fieldA, fieldB)
Defensive patterns
Strategy: type-guard
Validate before calling
int tA = metaA.getType();
if (tA != ValueMetaInterface.TYPE_NUMBER && tA != ValueMetaInterface.TYPE_INTEGER && tA != ValueMetaInterface.TYPE_BIGNUMBER) {
throw new KettleValueException("remainder(A,B) requires numeric field A, got type " + tA);
}
if (metaB.getNumber(dataB).doubleValue() == 0.0) {
throw new KettleValueException("remainder(A,B): divisor B is zero");
} Type guard
public static boolean isNumericMeta(ValueMetaInterface m) {
int t = m.getType();
return t == ValueMetaInterface.TYPE_NUMBER || t == ValueMetaInterface.TYPE_INTEGER || t == ValueMetaInterface.TYPE_BIGNUMBER;
} Try / catch
try {
r = ValueDataUtil.remainder(metaA, dataA, metaB, dataB);
} catch (KettleValueException e) {
logError("remainder failed: " + e.getMessage());
r = null;
} Prevention
- Convert both modulo operands to numeric types before the calculation.
- Check for a zero divisor before computing remainder/modulo.
- Pin field types with Select Values metadata steps immediately after inputs.
When it happens
Trigger: Calling ValueDataUtil.remainder(metaA, dataA, metaB, dataB) (Calculator 'A % B' / remainder) where metaA.getType() (or an operand) is not TYPE_NUMBER, TYPE_INTEGER, or TYPE_BIGNUMBER — e.g. String "10" % String "3".
Common situations: Modulo on string-typed IDs read from CSV; remainder of values that were re-typed to String by an upstream step; using date or boolean fields in a modulo calculation.
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
- The 'abs' function only works on numeric data
- The 'ceil' function only works on numeric data
- The 'combination1' function only works on numeric data
- The 'combination2' function only works on numeric data
- The 'floor' function only works on numeric data
AI-assisted analysis of pentaho/pentaho-kettle@f3058517a1 (2026-09-13).
Data as JSON: /api/errors/d13f05fe7e941be5.
Report an issue: GitHub.
Appendix: source
Thrown at core/src/main/java/org/pentaho/di/core/row/ValueDataUtil.java:1417
* @throws KettleValueException
*/
public static Object remainder( 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() % metaB.getNumber( dataB ).doubleValue() );
case ValueMetaInterface.TYPE_INTEGER:
return new Long( metaA.getInteger( dataA ) % metaB.getInteger( dataB ) );
case ValueMetaInterface.TYPE_BIGNUMBER:
BigDecimal aValue = metaA.getBigNumber( dataA );
BigDecimal bValue = metaA.getBigNumber( dataB );
BigDecimal result = aValue.remainder( bValue, MathContext.UNLIMITED );
return removeTrailingZeroFractionOrScale( result );
default:
throw new KettleValueException( "The 'remainder' function only works on numeric data" );
}
}
public static Object nvl( ValueMetaInterface metaA, Object dataA, ValueMetaInterface metaB, Object dataB ) throws KettleValueException {
switch ( metaA.getType() ) {
case ValueMetaInterface.TYPE_STRING:
if ( dataA == null ) {
return metaB.getString( dataB );
} else {
return metaA.getString( dataA );
}
case ValueMetaInterface.TYPE_NUMBER:
if ( dataA == null ) {
return metaB.getNumber( dataB );
} else {
return metaA.getNumber( dataA );
}View on GitHub (pinned to f3058517a1)