pentaho/pentaho-kettle · error · KettleException

JobExecutor.UnexpectedError

JobExecutor.UnexpectedError

Error message

JobExecutor.UnexpectedError

What it means

Generic catch-all wrapper in JobExecutor.processRow: any unexpected Exception thrown while executing the step (including failures inside executeJob() such as the nested job failing to start) is rethrown as a KettleException with the generic 'Unexpected error' message. The original cause is attached as the nested exception, so the real reason is in the cause chain.

Solutions

  1. Read the full stack trace and inspect the 'Caused by' entries to find the real root-cause exception
  2. Verify the nested job path/name configured in the step is valid and loadable in the current environment
  3. Run the nested job standalone to confirm it executes without errors
  4. Enable Kettle debug logging to capture the exact step where the failure originates

Example fix

// diagnosing
log layout shows:
JobExecutor.UnexpectedError
  Caused by: KettleException: Error loading job from repository : /jobs/load_orders
// after
Fix the nested job definition or its path; this error itself has no code fix — it is a wrapper
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate the nested job is loadable before executing the parent transformation
JobMeta jobMeta = new JobMeta(jobPath, repository, metaStore);
if ( jobMeta == null || Const.isEmpty(jobMeta.getName()) ) {
  throw new IllegalStateException("Nested job " + jobPath + " cannot be loaded");
}

Type guard

boolean hasRootCause(KettleException e, Class<? extends Exception> causeType) {
  Throwable t = e;
  while ( (t = t.getCause()) != null ) { if ( causeType.isInstance(t) ) return true; }
  return false;
}

Try / catch

try {
  executeNestedJob();
} catch ( KettleException e ) {
  // UnexpectedError is a wrapper: always walk the cause chain for the real reason
  Throwable root = e;
  while ( root.getCause() != null ) { root = root.getCause(); }
  logError("JobExecutor unexpected failure, root cause: " + root.getMessage(), root);
}

Prevention

When it happens

Trigger: Any unhandled Exception escaping processRow other than the specific typed errors (e.g. GroupFieldNotFound) — most commonly an exception from executeJob(), job metadata loading, or result-row processing.

Common situations: Nested job fails to load from repository or file; null or missing job specification; errors during row grouping buffers; Kettle internal bugs; the true root cause is only visible in the 'cause' of this exception in the log.

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/074b9f9722a0a500. Report an issue: GitHub.

Appendix: source

Thrown at engine/src/main/java/org/pentaho/di/trans/steps/jobexecutor/JobExecutor.java:173

        }
      }

      // Add next value AFTER job execution, in case we are grouping by field (see PDI-14958),
      // and BEFORE checking size of a group, in case we are grouping by size (see PDI-14121).
      data.groupBuffer.add( new RowMetaAndData( getInputRowMeta(), row ) ); // should we clone for safety?

      // Grouping by size.
      // If group buffer size exceeds specified limit, then execute job and flush group buffer.
      if ( data.groupSize > 0 ) {
        // Pass all input rows...
        if ( data.groupBuffer.size() >= data.groupSize ) {
          executeJob();
        }
      }

      return true;
    } catch ( Exception e ) {
      throw new KettleException( BaseMessages.getString( PKG, "JobExecutor.UnexpectedError" ), e );
    }
  }

  private void executeJob() throws KettleException {

    // If we got 0 rows on input we don't really want to execute the job
    //
    if ( data.groupBuffer.isEmpty() ) {
      return;
    }

    data.groupTimeStart = System.currentTimeMillis();

    if ( first ) {
      discardLogLines( data );
    }

    data.executorJob = createJob( meta.getRepository(), data.executorJobMeta, this );

View on GitHub (pinned to f3058517a1)