pentaho/pentaho-kettle · error · KettleStepException
SynchronizeAfterMerge.Exception.FieldRequired…
Error message
SynchronizeAfterMerge.Exception.FieldRequired (keyStream2[i])
What it means
Same family as the keyStream lookup failure, but for the second key field (keyStream2[i]). The lookup for keyStream2 only matters when the key condition is BETWEEN, which requires two fields; if keyStream2[i] cannot be found in the input rows and the condition is BETWEEN, processRow() throws KettleStepException.
Solutions
- Open the step and re-select Key stream 2 fields for entries using the BETWEEN condition
- If BETWEEN is no longer needed, change the key condition (e.g., to '=' ) so no second field is required
- Add a Select values rename step upstream to restore the expected field name
- Preview the upstream step output and verify the second field name exists and matches exactly
Example fix
// before condition: BETWEEN, keyStream2: end_date (renamed upstream to date_end) -> throw // after condition: BETWEEN, keyStream2: date_end // or change condition to '=' if the second bound is unnecessary
Defensive patterns
Strategy: validation
Validate before calling
var m = stepMeta.getStepMetaInterface();
for (var i = 0; i < m.getKeyStream2().length; i++) {
if ("BETWEEN".equalsIgnoreCase(m.getKeyCondition()[i]) && transMeta.getPrevStepFields(stepMeta).indexOfValue(m.getKeyStream2()[i]) < 0) {
throw new Error("Key stream 2 field missing: " + m.getKeyStream2()[i]);
}
} Type guard
function betweenKeysValid(meta, rowMeta) { return meta.getKeyStream2().every(function(k, i) { return !"BETWEEN".equalsIgnoreCase(meta.getKeyCondition()[i]) || rowMeta.indexOfValue(k) >= 0; }); } Try / catch
try { trans.execute(...); } catch (KettleException e) { if (e.getMessage().contains("keyStream2") || e.getMessage().contains("FieldRequired")) { log.error("Fix BETWEEN key field 2: " + e.getMessage()); } else throw e; } Prevention
- Only use BETWEEN conditions when both bound fields exist in the stream
- Change condition to '=' when a second bound field is unnecessary
- Preview upstream fields after any schema change
- Avoid hand-editing step XML; use the dialog
When it happens
Trigger: A key entry with condition BETWEEN has its second field (Key stream 2) set to a name absent from the input row layout — typically because the field was renamed, removed, or never existed upstream.
Common situations: Configuring BETWEEN comparisons on date/numeric ranges where one bound field was renamed upstream; pasting a step config from another transformation with a different stream schema.
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
- SynchronizeAfterMerge.Exception.FieldRequired (keyStream[i])
- SynchronizeAfterMerge.Exception.FieldRequired…
- ColumnExists.Exception.CouldnotFindField
- CombinationLookup.Exception.FieldNotFound
- ConcatFields.Error.TargetFieldNotFoundOutputStream
AI-assisted analysis of pentaho/pentaho-kettle@f3058517a1 (2026-09-13).
Data as JSON: /api/errors/9b6b108e1d55f7ca.
Report an issue: GitHub.
Appendix: source
Thrown at engine/src/main/java/org/pentaho/di/trans/steps/synchronizeaftermerge/SynchronizeAfterMerge.java:720
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++) {
ValueMetaInterface insValue = data.insertRowMeta.searchValueMeta( meta.getUpdateLookup()[i] );
if ( insValue == null ) { // Don't add twice!
// we already checked that this value exists so it's probably safe to ignore lookup failure...
ValueMetaInterface insertValue = data.inputRowMeta.searchValueMeta( meta.getUpdateStream()[i] ).clone();
insertValue.setName( meta.getUpdateLookup()[i] );
data.insertRowMeta.addValueMeta( insertValue );
} else {View on GitHub (pinned to f3058517a1)