{"record":{"id":"9694f8b72e2b729e","repo":"pentaho/pentaho-kettle","slug":"append-exception-invalidlayoutdetected","errorCode":null,"errorMessage":"Append.Exception.InvalidLayoutDetected","messagePattern":"Append\\.Exception\\.InvalidLayoutDetected","errorType":"exception","errorClass":"KettleException","httpStatus":null,"severity":"error","filePath":"plugins/core/impl/src/main/java/org/pentaho/di/trans/steps/append/Append.java","lineNumber":87,"sourceCode":"    if ( data.processTail ) {\n      input = getRowFrom( data.tailRowSet );\n      if ( input == null ) {\n        setOutputDone();\n        return false;\n      }\n      if ( data.outputRowMeta == null ) {\n        data.outputRowMeta = data.tailRowSet.getRowMeta();\n      }\n\n      if ( data.firstTail ) {\n        data.firstTail = false;\n\n        // Check here for the layout (which has to be the same) when we\n        // read the first row of the tail.\n        try {\n          checkInputLayoutValid( data.headRowSet.getRowMeta(), data.tailRowSet.getRowMeta() );\n        } catch ( KettleRowException e ) {\n          throw new KettleException( BaseMessages.getString( PKG, \"Append.Exception.InvalidLayoutDetected\" ), e );\n        }\n      }\n    }\n\n    if ( input != null ) {\n      putRow( data.outputRowMeta, input );\n    }\n\n    if ( checkFeedback( getLinesRead() ) ) {\n      if ( log.isBasic() ) {\n        logBasic( BaseMessages.getString( PKG, \"AppendRows.LineNumber\" ) + getLinesRead() );\n      }\n    }\n\n    return true;\n  }\n\n  /**","sourceCodeStart":69,"sourceCodeEnd":105,"githubUrl":"https://github.com/pentaho/pentaho-kettle/blob/f3058517a153da500bf4551f46d79b91bf8ec552/plugins/core/impl/src/main/java/org/pentaho/di/trans/steps/append/Append.java#L69-L105","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before: tail branch has mismatched type\n// after: add Select Values (Metadata) on the tail branch\ntailStep: SelectValues -> set 'amount' to Integer to match head branch","handlingStrategy":"validation","validationCode":"// Compare both branch layouts before executing the Append step\nRowMetaInterface head = transMeta.getStepFields(headStepMeta);\nRowMetaInterface tail = transMeta.getStepFields(tailStepMeta);\nif (head.size() != tail.size()) throw new IllegalStateException(\"Branch field count mismatch: \"\n  + head.getFieldNames().length + \" vs \" + tail.getFieldNames().length);\nfor (int i = 0; i < head.size(); i++) {\n  if (!head.getValueMeta(i).getType().equals(tail.getValueMeta(i).getType())) {\n    throw new IllegalStateException(\"Type mismatch at field \" + i + \": \"\n      + head.getValueMeta(i).getName() + \" vs \" + tail.getValueMeta(i).getName());\n  }\n}","typeGuard":"boolean layoutsMatch(RowMetaInterface a, RowMetaInterface b) {\n  if (a.size() != b.size()) return false;\n  for (int i = 0; i < a.size(); i++) {\n    if (a.getValueMeta(i).getType() != b.getValueMeta(i).getType()) return false;\n  }\n  return true;\n}","tryCatchPattern":"try {\n  trans.startExecution();\n} catch (KettleException e) {\n  if (e.getMessage().contains(\"InvalidLayoutDetected\") && e.getCause() instanceof KettleRowException) {\n    log.error(\"Append layout mismatch: \" + e.getCause().getMessage());\n  }\n  throw e;\n}","preventionTips":["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"],"tags":["row-layout","schema-mismatch","streaming","kettle"],"backgroundTag":"schema-validation-failed","analyzedSha":"f3058517a153da500bf4551f46d79b91bf8ec552","analyzedAt":"2026-09-13T14:04:16.340Z","contentChangedAt":"2026-09-13T14:04:16.340Z","schemaVersion":2},"datasetVersion":"2026-09-20T23:17:15.980Z"}