{"record":{"id":"296a505b75fea8ff","repo":"pentaho/pentaho-kettle","slug":"the-number-of-hops-read-hops-was-not-the-number-we-expected","errorCode":null,"errorMessage":"The number of hops read [${hops}] was not the number we expected [${expected}]","messagePattern":"The number of hops read \\[(.+?)\\] was not the number we expected \\[(.+?)\\]","errorType":"exception","errorClass":"KettleException","httpStatus":null,"severity":"error","filePath":"plugins/pur/core/src/main/java/org/pentaho/di/repository/pur/TransDelegate.java","lineNumber":443,"sourceCode":"      String stepFromName = getString( hopNode, TRANS_HOP_FROM );\n      String stepToName = getString( hopNode, TRANS_HOP_TO );\n      boolean enabled = true;\n      if ( hopNode.hasProperty( TRANS_HOP_ENABLED ) ) {\n        enabled = hopNode.getProperty( TRANS_HOP_ENABLED ).getBoolean();\n      }\n\n      StepMeta stepFrom = StepMeta.findStep( transMeta.getSteps(), stepFromName );\n      StepMeta stepTo = StepMeta.findStep( transMeta.getSteps(), stepToName );\n\n      // Make sure to only accept valid hops PDI-5519\n      //\n      if ( stepFrom != null && stepTo != null ) {\n        transMeta.addTransHop( new TransHopMeta( stepFrom, stepTo, enabled ) );\n      }\n\n    }\n    if ( transMeta.nrTransHops() != nrHops ) {\n      throw new KettleException( \"The number of hops read [\" + transMeta.nrTransHops()\n          + \"] was not the number we expected [\" + nrHops + \"]\" );\n    }\n\n    // Load the details at the end, to make sure we reference the databases correctly, etc.\n    //\n    loadTransformationDetails( rootNode, transMeta );\n    loadDependencies( rootNode, transMeta );\n\n    transMeta.eraseParameters();\n\n    DataNode paramsNode = rootNode.getNode( NODE_PARAMETERS );\n\n    int count = (int) paramsNode.getProperty( PROP_NR_PARAMETERS ).getLong();\n    for ( int idx = 0; idx < count; idx++ ) {\n      DataNode paramNode = paramsNode.getNode( TRANS_PARAM_PREFIX + idx );\n      String key = getString( paramNode, PARAM_KEY );\n      String def = getString( paramNode, PARAM_DEFAULT );\n      String desc = getString( paramNode, PARAM_DESC );","sourceCodeStart":425,"sourceCodeEnd":461,"githubUrl":"https://github.com/pentaho/pentaho-kettle/blob/f3058517a153da500bf4551f46d79b91bf8ec552/plugins/pur/core/src/main/java/org/pentaho/di/repository/pur/TransDelegate.java#L425-L461","documentation":"TransDelegate.dataNodeToElement reads hops (step connections) from the stored DataNode and verifies that transMeta.nrTransHops() equals the number of hop nodes stored (nrHops). A mismatch throws this KettleException. It usually means some hops were skipped during load — typically because their from/to steps were not found in the transformation.","triggerScenarios":"Loading a transformation where a hop references a step that failed to load or is absent, so addTransHop is skipped and nrTransHops < nrHops; or stored data contains hops whose step names don't resolve.","commonSituations":"Transformations edited or migrated between repositories where step names changed, partially failed imports, manually edited repository nodes, or loading content saved by a different Kettle version.","solutions":["Open and re-save the transformation from a working copy so steps and hops are serialized together.","Inspect NODE_HOPS entries in the stored data for hops referencing missing/renamed steps.","Restore the transformation from a previous repository version.","Verify the transformation loads in the same Kettle version it was saved with; version mismatches can change hop serialization."],"exampleFix":"// before\nif ( stepFrom != null && stepTo != null ) {\n  transMeta.addTransHop( new TransHopMeta( stepFrom, stepTo, enabled ) );\n} // later throws when count mismatches\n// after\nif ( stepFrom != null && stepTo != null ) {\n  transMeta.addTransHop( new TransHopMeta( stepFrom, stepTo, enabled ) );\n} else {\n  log.logError( \"Hop references missing step; from=\" + fromName + \" to=\" + toName + \" in \" + transMeta.getName() );\n  nrHops--; // align expected count with skippable hops, or fail with a clear message\n}","handlingStrategy":"try-catch","validationCode":"// verify each hop's steps exist before load-time consistency check\nfor ( String stepName : hopStepNames ) {\n  if ( transMeta.findStep( stepName ) == null ) throw new KettleException( \"Hop references missing step: \" + stepName );\n}","typeGuard":null,"tryCatchPattern":"try {\n  delegate.dataNodeToElement( node, transMeta );\n} catch ( KettleException e ) {\n  if ( e.getMessage().contains( \"number of hops read\" ) ) {\n    logError( \"Hop/step mismatch; check for renamed or missing steps in \" + transMeta.getName() );\n  } else { throw e; }\n}","preventionTips":["Re-save transformations after renaming steps to keep hops consistent","Avoid out-of-band edits to repository content","Use version history to recover from corrupted loads","Migrate content with proper import/export tools, not raw node copies"],"tags":["repository","transformation","hops","data-integrity","pentaho-kettle"],"backgroundTag":"internal-invariant-violation","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"}