pentaho/pentaho-kettle · critical · KettleException

sqlldr returned an error (exit code )

Error message

sqlldr returned an error (exit code )

What it means

Thrown by checkExitVal when sqlldr exits with a code other than EX_SUCC (0) or EX_WARN (2) and failOnError is enabled. The message includes the actual exit code (rendered as empty in this message variant because the code was concatenated at runtime). It means the bulk load failed outright — no useful load occurred.

Solutions

  1. Check the sqlldr .log file for the underlying ORA- error and fix the database-side cause (credentials, table, privileges)
  2. Verify sqlldr is installed and on PATH for the user running Pentaho (exit code 127/2 from the shell)
  3. Verify the OraBulkLoaderMeta connection settings (host, port, database/SID, user, password)
  4. Keep failOnError=true (default) so failures are surfaced; if exit code came from a transient issue, correct it and re-run

Example fix

// before
throw new KettleException( "sqlldr returned an error (exit code " + exitVal + ")" );
// after
// capture and expose the sqlldr log so the real ORA- error is visible
throw new KettleException( "sqlldr returned an error (exit code " + exitVal + "), see " + meta.getLogFile() );
Defensive patterns

Strategy: try-catch

Validate before calling

// verify prerequisites before the step runs
assert new java.io.File( sqlldrPath ).exists() || streamCommandOnPath( "sqlldr" );
assert transMeta.findDatabase( connName ) != null;

Try / catch

try {
  step.execute( meta, true );
} catch ( KettleException e ) {
  java.util.regex.Matcher m = java.util.regex.Pattern.compile( "exit code (\\d+)" ).matcher( e.getMessage() );
  if ( m.find() ) {
    int code = Integer.parseInt( m.group( 1 ) );
    // 1=fatal, 3=ORACLE errors, 4=OS errors — branch on code
  }
}

Prevention

When it happens

Trigger: sqlldr process exits with e.g. 1 (fatal error), 3 or 4 (ORA- errors, OS errors) while meta.isFailOnError() is true; called from execute() after waitFor() or from processRow() for the stream load method.

Common situations: Wrong Oracle credentials/SID in the connection; malformed control file; table does not exist or no INSERT privilege; sqlldr binary not on PATH (command exit codes like 127).

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


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

Appendix: source

Thrown at plugins/oracle-bulk-loader/impl/src/main/java/org/pentaho/di/trans/steps/orabulkloader/OraBulkLoader.java:461

      sb.append( " DIRECT=TRUE" );

      if ( getStepMeta().getCopies() > 1 || meta.isParallel() ) {
        sb.append( " PARALLEL=TRUE" );
      }
    }

    return sb.toString();
  }

  public void checkExitVal( int exitVal ) throws KettleException {
    if ( exitVal == EX_SUCC ) {
      return;
    }

    if ( meta.isFailOnWarning() && ( exitVal == EX_WARN ) ) {
      throw new KettleException( "sqlldr returned warning" );
    } else if ( meta.isFailOnError() && ( exitVal != EX_WARN ) ) {
      throw new KettleException( "sqlldr returned an error (exit code " + exitVal + ")" );
    }
  }

  public boolean execute( OraBulkLoaderMeta meta, boolean wait ) throws KettleException {
    Runtime rt = Runtime.getRuntime();

    try {
      sqlldrProcess = rt.exec( createCommandLine( meta, true ) );
      // any error message?
      StreamLogger errorLogger = new StreamLogger( sqlldrProcess.getErrorStream(), "ERROR" );

      // any output?
      StreamLogger outputLogger = new StreamLogger( sqlldrProcess.getInputStream(), "OUTPUT" );

      // kick them off
      errorLogger.start();
      outputLogger.start();

View on GitHub (pinned to f3058517a1)