pentaho/pentaho-kettle · error · KettleDatabaseException
SynchronizeAfterMerge.Exception.KeyCouldNotFound
SynchronizeAfterMerge.Exception.KeyCouldNotFound
Error message
SynchronizeAfterMerge.Exception.KeyCouldNotFound
What it means
Thrown in lookupValues() when the step executes the lookup SELECT for the key lookup (used to decide update vs insert) and no matching row is returned, while the operation is update/delete. data.lookupFailure is set and a KettleDatabaseException with the lookup key values is raised, because the step cannot perform an update/delete on a record that does not exist.
Solutions
- Verify the key field mappings (table field <-> stream field) in the step's 'Lookup keys' grid match the real target table columns.
- Normalize data types/whitespace of key values upstream (Trim strings, type conversions) so the lookup SELECT matches.
- Route such rows through a different path: use the step's error handling or filter, or change operation to 'insert' for missing keys.
- Enable the step's error handling (isDoingErrorHandling) so lookup failures go to the error stream instead of failing the transformation.
Example fix
// before: key column CHAR(10) padded, lookup compares unpadded value SELECT ... WHERE code = ? // after: trim upstream before the step RTrim(code) AS code FROM source;
Defensive patterns
Strategy: try-catch
Validate before calling
// Validate key existence before choosing update/delete
boolean keyExists = lookupKeyInTarget(keyValues); // SELECT 1 FROM target WHERE key = ?
if (!keyExists && operation.equals("update")) {
throw new ValidationException("Update requested but key not found: " + Arrays.toString(keyValues));
} Try / catch
catch (KettleDatabaseException e) {
if (e.getMessage().contains("KeyCouldNotFound")) {
// route row to insert path or error stream
} else { throw e; }
} Prevention
- Trim and type-cast key fields upstream so lookups match (CHAR padding, case).
- Double-check the key field mapping grid against real target columns.
- Enable step error handling so lookup misses go to an error stream.
When it happens
Trigger: performUpdate or performDelete is true; the db lookup query built from the configured key/lookup fields returns an empty ResultSet; the code enters the 'else' branch for update/delete without a found row and throws.
Common situations: Key values differ in type or padding (trailing spaces, CHAR vs VARCHAR) between source and target; case-sensitive comparison; key column values truncated; lookup keys misconfigured in the dialog; row was deleted by a concurrent process.
Understand the failure class
Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.
Related errors
- Error inserting row into table
- Error updating batch, first 10 errors: Errors encountered…
- GetXMLDataMeta.Exception.ErrorReadingRepository (localized…
- GetXMLDataMeta.Exception.ErrorSavingToRepository (localized…
- StepMeta.Exception.StepCouldNotBeLoaded
AI-assisted analysis of pentaho/pentaho-kettle@f3058517a1 (2026-09-13).
Data as JSON: /api/errors/b3f22796d6814fdc.
Report an issue: GitHub.
Appendix: source
Thrown at engine/src/main/java/org/pentaho/di/trans/steps/synchronizeaftermerge/SynchronizeAfterMerge.java:200
incrementLinesInput();
if ( add == null ) {
// nothing was found:
if ( data.stringErrorKeyNotFound == null ) {
data.stringErrorKeyNotFound =
BaseMessages.getString( PKG, "SynchronizeAfterMerge.Exception.KeyCouldNotFound" )
+ data.lookupParameterRowMeta.getString( lookupRow );
data.stringFieldnames = "";
for (int i = 0; i < data.lookupParameterRowMeta.size(); i++) {
if ( i > 0 ) {
data.stringFieldnames += ", ";
}
data.stringFieldnames += data.lookupParameterRowMeta.getValueMeta( i ).getName();
}
}
data.lookupFailure = true;
throw new KettleDatabaseException( BaseMessages.getString( PKG,
"SynchronizeAfterMerge.Exception.KeyCouldNotFound", data.lookupParameterRowMeta.getString(
lookupRow ) ) );
} else {
if ( log.isRowLevel() ) {
logRowlevel( BaseMessages.getString( PKG, "SynchronizeAfterMerge.Log.FoundRowForUpdate",
data.insertRowMeta.getString( row ) ) );
}
for (int i = 0; i < data.valuenrs.length; i++) {
if ( meta.getUpdate()[i].booleanValue() ) {
ValueMetaInterface valueMeta = data.inputRowMeta.getValueMeta( data.valuenrs[i] );
ValueMetaInterface retMeta = data.db.getReturnRowMeta().getValueMeta( i );
Object rowvalue = row[data.valuenrs[i]];
Object retvalue = add[i];
if ( valueMeta.compare( rowvalue, retMeta, retvalue ) != 0 ) {
updateorDelete = true;View on GitHub (pinned to f3058517a1)