pentaho/pentaho-kettle · error · KettleException

Append.Exception.InvalidLayoutDetected

Error message

Append.Exception.InvalidLayoutDetected

What it means

Thrown by the Append step's processRow() when the first row arrives on the tail stream and its row layout does not match the head stream's layout. checkInputLayoutValid() raises a KettleRowException (field count/order/types differ), which Append wraps as a KettleException with this message — the layout details are in the cause.

Solutions

  1. Compare the printed field layouts of both branches and align them: same number, order, names and types of fields (insert Select Values to reorder/rename/retype)
  2. Add or fix upstream steps (Calculator / Select Values with metadata type change) so the tail branch matches the head layout exactly
  3. Re-run Get Fields / refresh metadata on both steps in Spoon after any upstream change, then re-save the transformation
  4. Verify you wired the correct hops into the Append step's head and tail inputs

Example fix

// before: tail branch has mismatched type
// after: add Select Values (Metadata) on the tail branch
tailStep: SelectValues -> set 'amount' to Integer to match head branch
Defensive patterns

Strategy: validation

Validate before calling

// Compare both branch layouts before executing the Append step
RowMetaInterface head = transMeta.getStepFields(headStepMeta);
RowMetaInterface tail = transMeta.getStepFields(tailStepMeta);
if (head.size() != tail.size()) throw new IllegalStateException("Branch field count mismatch: "
  + head.getFieldNames().length + " vs " + tail.getFieldNames().length);
for (int i = 0; i < head.size(); i++) {
  if (!head.getValueMeta(i).getType().equals(tail.getValueMeta(i).getType())) {
    throw new IllegalStateException("Type mismatch at field " + i + ": "
      + head.getValueMeta(i).getName() + " vs " + tail.getValueMeta(i).getName());
  }
}

Type guard

boolean layoutsMatch(RowMetaInterface a, RowMetaInterface b) {
  if (a.size() != b.size()) return false;
  for (int i = 0; i < a.size(); i++) {
    if (a.getValueMeta(i).getType() != b.getValueMeta(i).getType()) return false;
  }
  return true;
}

Try / catch

try {
  trans.startExecution();
} catch (KettleException e) {
  if (e.getMessage().contains("InvalidLayoutDetected") && e.getCause() instanceof KettleRowException) {
    log.error("Append layout mismatch: " + e.getCause().getMessage());
  }
  throw e;
}

Prevention

When it happens

Trigger: processRow() reads the first row from the tail rowset, calls checkInputLayoutValid(headRowSet.getRowMeta(), tailRowSet.getRowMeta()), and the two RowMeta differ (different field count, names, order, or types), so the streams cannot be concatenated.

Common situations: Two input branches built from different tables/queries whose column sets drifted; a step added or removed in one branch only; a column type change upstream (e.g. Integer -> String) in one branch; running an old .ktr after someone modified one branch of the transformation.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of pentaho/pentaho-kettle@f3058517a1 (2026-09-13). Data as JSON: /api/errors/9694f8b72e2b729e. Report an issue: GitHub.

Appendix: source

Thrown at plugins/core/impl/src/main/java/org/pentaho/di/trans/steps/append/Append.java:87

    if ( data.processTail ) {
      input = getRowFrom( data.tailRowSet );
      if ( input == null ) {
        setOutputDone();
        return false;
      }
      if ( data.outputRowMeta == null ) {
        data.outputRowMeta = data.tailRowSet.getRowMeta();
      }

      if ( data.firstTail ) {
        data.firstTail = false;

        // Check here for the layout (which has to be the same) when we
        // read the first row of the tail.
        try {
          checkInputLayoutValid( data.headRowSet.getRowMeta(), data.tailRowSet.getRowMeta() );
        } catch ( KettleRowException e ) {
          throw new KettleException( BaseMessages.getString( PKG, "Append.Exception.InvalidLayoutDetected" ), e );
        }
      }
    }

    if ( input != null ) {
      putRow( data.outputRowMeta, input );
    }

    if ( checkFeedback( getLinesRead() ) ) {
      if ( log.isBasic() ) {
        logBasic( BaseMessages.getString( PKG, "AppendRows.LineNumber" ) + getLinesRead() );
      }
    }

    return true;
  }

  /**

View on GitHub (pinned to f3058517a1)