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
- 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)
- Add or fix upstream steps (Calculator / Select Values with metadata type change) so the tail branch matches the head layout exactly
- Re-run Get Fields / refresh metadata on both steps in Spoon after any upstream change, then re-save the transformation
- 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
- Keep both Append input branches structurally identical: same field count, order, names, types
- Insert Select Values (Metadata) steps to normalize types whenever either branch changes
- Refresh field metadata in Spoon after editing any upstream step in a branch
- Re-run check() on the transformation in CI before deploying changed transformations
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
- All input files need to have the same number of fields. File
- BaseStreamStepMeta.CheckResult.ResultStepMissing
- Could not find field in stream
- FixedTimeStreamWindow.SubtransFailed
- FlattenerMeta.Exception.UnableToLocateFieldInInputFields
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)