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

  1. Open the step and re-select Key stream 2 fields for entries using the BETWEEN condition
  2. If BETWEEN is no longer needed, change the key condition (e.g., to '=' ) so no second field is required
  3. Add a Select values rename step upstream to restore the expected field name
  4. 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

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


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)