pentaho/pentaho-kettle · error · KettleException

CloneRow.Log.NrClonesIsNull

Error message

CloneRow.Log.NrClonesIsNull

What it means

After emitting the original row, CloneRow reads the clone-count field from the current row via getInteger(). If that field's value is null it throws this KettleException ('NrClones is null'), since the number of clones cannot be determined.

Solutions

  1. Prepend a 'Replace null values' (or IF/Value Mapper) step to default the count field to 0 or a valid number before CloneRow
  2. Fix the upstream step so the count field is always populated (e.g. NVL/count(*) in SQL)
  3. Filter out or handle rows with NULL counts before CloneRow
  4. Coerce the field to Integer type upstream so getInteger never yields null

Example fix

// SQL upstream
// before: SELECT maybe_null_cnt AS clone_count FROM t
// after:  SELECT NVL(maybe_null_cnt, 0) AS clone_count FROM t
Defensive patterns

Strategy: validation

Validate before calling

Object v = inputRowMeta.getInteger(row, idx);
if (v == null) {
  // route row to error handling or default it before CloneRow
  v = 0L;
}

Try / catch

catch (KettleException e) { if (e.getMessage().contains("NrClonesIsNull")) { add Replace-null upstream and rerun; } }

Prevention

When it happens

Trigger: processRow with isNrCloneInField()==true where getInputRowMeta().getInteger(r, indexOfNrCloneField) returns null — the field exists but holds NULL for this row.

Common situations: Upstream steps (table input, JSON input, optional joins) producing NULL in the count column; using a field of a type that converts to null; forgetting a default/replace-null step.

Related errors


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

Appendix: source

Thrown at plugins/core/impl/src/main/java/org/pentaho/di/trans/steps/clonerow/CloneRow.java:131

      outputRowData = RowDataUtil.createResizedCopy( r, data.outputRowMeta.size() );
      int rowIndex = data.NrPrevFields;
      if ( meta.isAddCloneFlag() ) {
        // This row is not a clone but the original row
        outputRowData[ rowIndex ] = false;
        rowIndex++;
      }
      if ( meta.isAddCloneNum() ) {
        // This row is the original so let's identify it as the first one (zero)
        outputRowData[ rowIndex ] = 0L;
      }
    }

    putRow( data.outputRowMeta, outputRowData ); // copy row to output rowset(s);

    if ( meta.isNrCloneInField() ) {
      Long nrCloneFieldValue = getInputRowMeta().getInteger( r, data.indexOfNrCloneField );
      if ( nrCloneFieldValue == null ) {
        throw new KettleException( BaseMessages.getString( PKG, "CloneRow.Log.NrClonesIsNull" ) );
      } else {
        data.nrclones = nrCloneFieldValue;
        if ( log.isDebug() ) {
          logDebug( BaseMessages.getString( PKG, "CloneRow.Log.NrClones", "" + data.nrclones ) );
        }
      }
    }
    for ( int i = 0; i < data.nrclones && !isStopped(); i++ ) {
      // Output now all clones row
      outputRowData = r.clone();
      if ( data.addInfosToRow ) {
        // We need here to add more infos about clone rows
        outputRowData = RowDataUtil.createResizedCopy( r, data.outputRowMeta.size() );
        int rowIndex = data.NrPrevFields;
        if ( meta.isAddCloneFlag() ) {
          // This row is a clone row
          outputRowData[ rowIndex ] = true;
          rowIndex++;

View on GitHub (pinned to f3058517a1)