pentaho/pentaho-kettle · error · KettleException

The number of notes read

Error message

The number of notes read [${notes}] was not the number we expected [${expected}]

What it means

TransDelegate.dataNodeToElement rebuilds a TransMeta from a stored DataNode and counts the note (NotepadMeta) nodes it expects, then verifies transMeta.nrNotes() matches. If adding notes produced a different count than the stored node count, this KettleException is thrown, indicating the stored transformation data is inconsistent or notes were rejected during load.

Solutions

  1. Re-export/re-save the transformation from a working copy so its notes are re-serialized consistently.
  2. Inspect the stored NODE_NOTES data (PROP_XML of each note) for corrupt or duplicate entries.
  3. Compare the stored note count with the loaded count from the exception message to locate the discrepant note.
  4. Restore the transformation from a previous repository version.

Example fix

// before
DataNode noteNode : notesNode.getNodes() -> addNote( ... ); // count mismatch thrown
// after
for ( DataNode noteNode : notesNode.getNodes() ) {
  String xml = getString( noteNode, PROP_XML );
  if ( xml == null || xml.isEmpty() ) {
    log.logBasic( "Skipping empty note XML in transformation " + transMeta.getName() );
    continue; // and adjust expected nrNotes accordingly
  }
  transMeta.addNote( new NotePadMeta( XMLHandler.getSubNode( XMLHandler.loadXMLString( xml ), NotePadMeta.XML_TAG ) ) );
}
Defensive patterns

Strategy: try-catch

Validate before calling

int storedNotes = notesNode.getNodes().size();
// after load: transMeta.nrNotes() must equal storedNotes

Try / catch

try {
  delegate.dataNodeToElement( node, transMeta );
} catch ( KettleException e ) {
  if ( e.getMessage().contains( "number of notes read" ) ) {
    logError( "Corrupt transformation data; restoring previous version" );
    restorePreviousVersion( transMeta.getObjectId() );
  } else { throw e; }
}

Prevention

When it happens

Trigger: Loading a transformation from the PUR repository where the number of note nodes under NODE_NOTES differs from the resulting TransMeta note count — e.g., duplicate/invalid note XML causes addNote to skip or the stored data was modified out-of-band.

Common situations: Manually edited repository content in the Jackrabbit backend, imports/export round-trips that lost or duplicated notes, corrupted transformation nodes, or version mismatches between writer and reader.

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


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

Appendix: source

Thrown at plugins/pur/core/src/main/java/org/pentaho/di/repository/pur/TransDelegate.java:416

    for ( int i = 0; i < transMeta.nrSteps(); i++ ) {
      StepMeta stepMeta = transMeta.getStep( i );
      StepMetaInterface sii = stepMeta.getStepMetaInterface();
      if ( sii != null ) {
        sii.searchInfoAndTargetSteps( transMeta.getSteps() );
      }
    }

    // Read the notes...
    //
    DataNode notesNode = rootNode.getNode( NODE_NOTES );
    int nrNotes = (int) notesNode.getProperty( PROP_NR_NOTES ).getLong();
    for ( DataNode noteNode : notesNode.getNodes() ) {
      String xml = getString( noteNode, PROP_XML );
      transMeta
          .addNote( new NotePadMeta( XMLHandler.getSubNode( XMLHandler.loadXMLString( xml ), NotePadMeta.XML_TAG ) ) );
    }
    if ( transMeta.nrNotes() != nrNotes ) {
      throw new KettleException( "The number of notes read [" + transMeta.nrNotes()
          + "] was not the number we expected [" + nrNotes + "]" );
    }

    // Read the hops...
    //
    DataNode hopsNode = rootNode.getNode( NODE_HOPS );
    int nrHops = (int) hopsNode.getProperty( PROP_NR_HOPS ).getLong();
    for ( DataNode hopNode : hopsNode.getNodes() ) {
      String stepFromName = getString( hopNode, TRANS_HOP_FROM );
      String stepToName = getString( hopNode, TRANS_HOP_TO );
      boolean enabled = true;
      if ( hopNode.hasProperty( TRANS_HOP_ENABLED ) ) {
        enabled = hopNode.getProperty( TRANS_HOP_ENABLED ).getBoolean();
      }

      StepMeta stepFrom = StepMeta.findStep( transMeta.getSteps(), stepFromName );
      StepMeta stepTo = StepMeta.findStep( transMeta.getSteps(), stepToName );

View on GitHub (pinned to f3058517a1)