pentaho/pentaho-kettle · error · KettleException
The number of hops read [
Error message
The number of hops read [
What it means
After reading job hops (JobHopMeta) from the repository DataNode and adding them to jobMeta, JobDelegate asserts jobMeta.nrJobHops() equals the expected nrHops. A mismatch means stored hop entries disagree with the recorded hop count, so the job is considered corrupt and loading fails, preventing broken job graphs (orphaned hops, wrong evaluation conditions) from being used.
Solutions
- Restore the job from a backup or re-save it in PDI so hops and counts are rewritten consistently.
- Diff the stored NODE_HOPS children against nrHops to identify dropped hops, then fix the stored data.
- Match PDI client and Pentaho server versions to rule out serialization drift.
- Delete the job from the repository and re-import from a known-good .kjb export.
- Catch KettleException in automation and fall back to a cached/backup copy of the job metadata.
Example fix
// before
JobMeta jobMeta = (JobMeta) repository.loadJob(jobId, null).loadAll(); // throws on hop-count mismatch
// after
try {
jobMeta = (JobMeta) repository.loadJob(jobId, null).loadAll();
} catch (KettleException e) {
if (e.getMessage().startsWith("The number of hops read")) {
jobMeta = importJobFromFile(backupKjbPath);
} else {
throw e;
}
} Defensive patterns
Strategy: try-catch
Try / catch
try {
JobMeta jobMeta = (JobMeta) repository.loadJob(jobId, null).loadAll();
} catch (KettleException e) {
if (e.getMessage().startsWith("The number of hops read")) {
// hop list inconsistent -> re-import or restore job
} else {
throw e;
}
} Prevention
- Prevent concurrent modifications of the same job in the repository.
- Never edit repository content stores directly.
- Keep PDI client and Pentaho server versions synchronized.
- Regularly export jobs to .kjb files as a recovery path.
When it happens
Trigger: Loading a job whose DataNode's NODE_HOPS children count differs from the persisted nrHops — caused by partial/interrupted saves, concurrent modifications, or version drift in how hops were serialized into the repository.
Common situations: Repository corruption after aborted saves; two users editing the same job concurrently; importing jobs exported from a different PDI/Pentaho version; direct manipulation of repository storage.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- The number of job entry copies read [
- The number of notes read [
- AbsSecurityProvider.ERROR_0002_UNABLE_TO_ACCESS_IS_ALLOWED
- AddSequenceMeta.Exception.UnableToReadStepInfo
- AddSequenceMeta.Exception.UnableToSaveStepInfo
AI-assisted analysis of pentaho/pentaho-kettle@f3058517a1 (2026-09-13).
Data as JSON: /api/errors/b7c48073ac2f96c6.
Report an issue: GitHub.
Appendix: source
Thrown at plugins/pur/core/src/main/java/org/pentaho/di/repository/pur/JobDelegate.java:293
}
boolean unconditional = true;
if ( hopNode.hasProperty( JOB_HOP_UNCONDITIONAL ) ) {
unconditional = hopNode.getProperty( JOB_HOP_UNCONDITIONAL ).getBoolean();
}
JobEntryCopy copyFrom = jobMeta.findJobEntry( copyFromName, copyFromNr, true );
JobEntryCopy copyTo = jobMeta.findJobEntry( copyToName, copyToNr, true );
JobHopMeta jobHopMeta = new JobHopMeta( copyFrom, copyTo );
jobHopMeta.setEnabled( enabled );
jobHopMeta.setEvaluation( evaluation );
jobHopMeta.setUnconditional( unconditional );
jobMeta.addJobHop( jobHopMeta );
}
if ( jobMeta.nrJobHops() != nrHops ) {
throw new KettleException( "The number of hops read [" + jobMeta.nrJobHops()
+ "] was not the number we expected [" + nrHops + "]" );
}
// Load the details at the end, to make sure we reference the databases correctly, etc.
//
loadJobMetaDetails( rootNode, jobMeta );
jobMeta.eraseParameters();
DataNode paramsNode = rootNode.getNode( NODE_PARAMETERS );
int count = (int) paramsNode.getProperty( PROP_NR_PARAMETERS ).getLong();
for ( int idx = 0; idx < count; idx++ ) {
DataNode paramNode = paramsNode.getNode( PARAM_PREFIX + idx );
String key = getString( paramNode, PARAM_KEY );
String def = getString( paramNode, PARAM_DEFAULT );
String desc = getString( paramNode, PARAM_DESC );
jobMeta.addParameterDefinition( key, def, desc );
}
}View on GitHub (pinned to f3058517a1)