pentaho/pentaho-kettle · error · KettleValueException
<toString()> : couldn't convert String to number with…
Error message
<toString()> : couldn't convert String to number with <empty format|format [getFormatMask()]>: unexpected character found at position <parsePosition.getErrorIndex() + 1> for value [<string>]
What it means
Thrown by ValueMetaBase.convertStringToNumber when DecimalFormat.parse leaves unconsumed characters: ParsePosition.getIndex() < string.length() means trailing junk after the numeric portion. The message reports the 1-based position of the unexpected character, the format mask, and the input string.
Solutions
- Trim/clean the input string (remove trailing units, symbols, non-breaking spaces) before conversion
- Set the correct format mask with setConversionMask matching the data (including grouping/decimal separators)
- Set the proper locale via setConversionLocale so separators match
- Pre-validate with a regex like ^[+-]?[0-9.,]+$ before calling getNumber
Example fix
// before
Double d = valueMeta.getNumber("1.234,56 kg"); // throws at position 9
// after
String cleaned = raw.replaceAll("[^0-9,.-]", "").trim();
Double d = valueMeta.getNumber(cleaned); Defensive patterns
Strategy: validation
Validate before calling
static boolean isFullyNumeric(String s, char groupSep, char decSep) {
if (s == null || s.isEmpty()) return false;
String t = s.trim();
String regex = "^[+-]?[0-9" + java.util.regex.Pattern.quote(String.valueOf(groupSep))
+ java.util.regex.Pattern.quote(String.valueOf(decSep)) + "]+$";
return t.matches(regex);
} Type guard
static boolean parsesFully(java.text.DecimalFormat fmt, String s) {
java.text.ParsePosition pos = new java.text.ParsePosition(0);
Number n = fmt.parse(s, pos);
return n != null && pos.getIndex() == s.trim().length();
} Try / catch
try {
Number n = valueMeta.getNumber(str);
} catch (KettleValueException e) {
log.error("Bad number '" + str + "': " + e.getMessage());
routeToErrorRow(str, "non-numeric trailing characters");
} Prevention
- Trim strings and strip currency symbols/units before conversion
- Match the mask's decimal/grouping separators to the data's locale
- Pre-validate with DecimalFormat + ParsePosition checking getIndex() == length
- Enable transformation error handling to catch bad rows
When it happens
Trigger: Calling getNumber()/convertStringToNumber() with a string like '123abc', '1 234,5 EUR', or a value that does not fully match getFormatMask() (grouping separators, currency symbols, or spaces in the wrong place) when no lenient 'format.parse(string)' path is used.
Common situations: Export files with trailing units ('kg', '%') or currency symbols; wrong decimal/grouping separator for the configured locale; extra whitespace or BOM at the end of the value; mask changed after the data format changed.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- <toString()> : couldn't convert Number to String
- <toString()> : couldn't convert String to number
- <toString()> : couldn't convert String to number …
- AnalyticQueryMeta.Exception.UnableToLoadStepInfoFromXML
- Could not convert the given String :
AI-assisted analysis of pentaho/pentaho-kettle@f3058517a1 (2026-09-13).
Data as JSON: /api/errors/9a4a48bd40f1b491.
Report an issue: GitHub.
Appendix: source
Thrown at core/src/main/java/org/pentaho/di/core/row/value/ValueMetaBase.java:1029
string = Const.trimToType( string, getTrimType() ); // see if trimming needs
// to be performed before
// conversion
if ( Utils.isEmpty( string ) ) {
return null;
}
try {
DecimalFormat format = getDecimalFormat( false );
Number number;
if ( lenientStringToNumber ) {
number = format.parse( string );
} else {
ParsePosition parsePosition = new ParsePosition( 0 );
number = format.parse( string, parsePosition );
if ( parsePosition.getIndex() < string.length() ) {
throw new KettleValueException( toString()
+ " : couldn't convert String to number with " + ( ( getFormatMask() == null ) ? "empty format" : "format [" + getFormatMask() + "]" ) + ": unexpected character found at position "
+ ( parsePosition.getErrorIndex() + 1 ) + " for value [" + string + "]" );
}
}
return new Double( number.doubleValue() );
} catch ( Exception e ) {
throw new KettleValueException( toString() + " : couldn't convert String to number ", e );
}
}
@Override
public synchronized SimpleDateFormat getDateFormat() {
return getDateFormat( getType() );
}
private synchronized SimpleDateFormat getDateFormat( int valueMetaType ) {View on GitHub (pinned to f3058517a1)