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
- Convert the non-numeric field to Number/Integer (Select values step) before the 'A-B%' calculation
- Fix the input step's field type so percentages are parsed as numeric at read time
- Check the Calculator step's field A/B mappings for the 'A-B%' line
- 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
- Percent fields (discount, tax) imported from spreadsheets are the usual string culprits — type them at read time
- Use BigNumber for the base amount A in financial contexts
- Verify Calculator mappings after upstream renames
- Fail fast with a Filter/validation step on typeof/Is Numeric conditions
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
- The 'A+B%' function only works on numeric data
- The 'A/B in %' function only works on numeric data
- The 'sqrt' function only works on numeric data.
- Calculator.Error.NoNameField
- Calculator.Error.UnableFindField
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)