pentaho/pentaho-kettle · error · KettleStepException
SynchronizeAfterMerge.Exception.FieldRequired (keyStream[i])
Error message
SynchronizeAfterMerge.Exception.FieldRequired (keyStream[i])
What it means
The Synchronize After Merge step could not find one of the configured key fields (keyStream[i]) in the incoming row stream, and the key condition for that key still requires a field value. processRow() resolves each key field name to a row index during initialization of the row lookup; a negative index means the transformation wiring no longer matches the step configuration. It throws KettleStepException to fail fast before any rows are synchronized.
Solutions
- Open the step and re-select the key fields from the available input fields so the names match the actual upstream stream
- Insert or fix a 'Select values' (rename) step so the upstream field name equals the configured key stream name
- Check field-name case sensitivity; Kettle field lookups are case-sensitive so make casing match exactly
- Re-run upstream steps' preview to confirm the field exists at the point this step runs
Example fix
// before (upstream renamed customer_id to custId) Key stream: customer_id -> indexOfValue returns -1 -> KettleStepException // after Option A: update step config Key stream to custId Option B: upstream Select values: Rename custId -> customer_id
Defensive patterns
Strategy: validation
Validate before calling
// PDI JS / before running the step
var fields = transMeta.getPrevStepFields(stepMeta);
for (var i = 0; i < stepMeta.getStepMetaInterface().getKeyStream().length; i++) {
var ks = stepMeta.getStepMetaInterface().getKeyStream()[i];
var cond = stepMeta.getStepMetaInterface().getKeyCondition()[i];
if (fields.indexOfValue(ks) < 0 && !"IS NULL".equalsIgnoreCase(cond) && !"IS NOT NULL".equalsIgnoreCase(cond)) {
throw new Error("Key field missing from input: " + ks);
}
} Type guard
function hasField(rowMeta, name) { return rowMeta != null && name != null && rowMeta.indexOfValue(name) >= 0; } Try / catch
try { trans.execute(...); } catch (KettleException e) { if (e.getMessage().contains("FieldRequired")) { log.error("Re-check key field mapping: " + e.getMessage()); /* fix mapping */ } else throw e; } Prevention
- Always pick key fields from the dialog dropdown rather than typing names
- Re-open and re-save the step after renaming any upstream field
- Preview the immediately preceding step before running the transformation
- Keep field names consistent across environments with a naming convention
When it happens
Trigger: The key field name in the step's 'Key stream' grid does not exist in the input RowMeta: the upstream step was renamed/removed, a Rename field step changed the name, the field casing differs, or the field was deleted from a preceding Select values step.
Common situations: Renaming a field upstream after configuring this step; pointing the step at a different table/file whose schema lacks the key columns; copying a transformation between environments where upstream metadata differs; typos in the key field name.
Understand the failure class
Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.
Related errors
- GetPreviousRowField.Exception.FieldRequired
- SynchronizeAfterMerge.Error.SameColumnInsertedTwice
- SynchronizeAfterMerge.Exception.FieldRequired…
- SynchronizeAfterMerge.Exception.FieldRequired…
- ChangeFileEncoding.Error.CreatingFile
AI-assisted analysis of pentaho/pentaho-kettle@f3058517a1 (2026-09-13).
Data as JSON: /api/errors/9702cbb1e953d6ac.
Report an issue: GitHub.
Appendix: source
Thrown at engine/src/main/java/org/pentaho/di/trans/steps/synchronizeaftermerge/SynchronizeAfterMerge.java:713
data.updateValue = environmentSubstitute( meta.getOrderUpdate() );
data.deleteValue = environmentSubstitute( meta.getOrderDelete() );
data.insertRowMeta = new RowMeta();
// lookup the values!
if ( log.isDebug() ) {
logDebug( BaseMessages.getString( PKG, "SynchronizeAfterMerge.Log.CheckingRow" ) + Arrays.toString( nextRow ) );
}
data.keynrs = new int[meta.getKeyStream().length];
data.keynrs2 = new int[meta.getKeyStream().length];
for (int i = 0; i < meta.getKeyStream().length; i++) {
data.keynrs[i] = data.inputRowMeta.indexOfValue( meta.getKeyStream()[i] );
if ( data.keynrs[i] < 0 && // couldn't find field!
!"IS NULL".equalsIgnoreCase( meta.getKeyCondition()[i] ) && // No field needed!
!"IS NOT NULL".equalsIgnoreCase( meta.getKeyCondition()[i] ) // No field needed!
) {
throw new KettleStepException( BaseMessages.getString( PKG, "SynchronizeAfterMerge.Exception.FieldRequired",
meta.getKeyStream()[i] ) );
}
data.keynrs2[i] = data.inputRowMeta.indexOfValue( meta.getKeyStream2()[i] );
if ( data.keynrs2[i] < 0 && // couldn't find field!
"BETWEEN".equalsIgnoreCase( meta.getKeyCondition()[i] ) // 2 fields needed!
) {
throw new KettleStepException( BaseMessages.getString( PKG, "SynchronizeAfterMerge.Exception.FieldRequired",
meta.getKeyStream2()[i] ) );
}
if ( log.isDebug() ) {
logDebug( BaseMessages.getString( PKG, "SynchronizeAfterMerge.Log.FieldHasDataNumbers", meta
.getKeyStream()[i] ) + data.keynrs[i] );
}
}
// Insert the update fields: just names. Type doesn't matter!
for (int i = 0; i < meta.getUpdateLookup().length; i++) {View on GitHub (pinned to f3058517a1)