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

  1. Open the step and re-select the key fields from the available input fields so the names match the actual upstream stream
  2. Insert or fix a 'Select values' (rename) step so the upstream field name equals the configured key stream name
  3. Check field-name case sensitivity; Kettle field lookups are case-sensitive so make casing match exactly
  4. 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

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


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)