apache/seatunnel · error · SeaTunnelRuntimeException

API-09

API-09

Error message

Handle save mode failed

What it means

Thrown when executing a sink's SaveModeHandler — the pre-write actions like auto-create-table, drop-table, or clear-data — fails. The handler is opened, executed via SaveModeExecuteWrapper, and any Exception is rethrown as a SeaTunnelRuntimeException with code API-09 ('Handle save mode failed'). The root cause (typically a database error) is attached.

Solutions

  1. Read the 'Caused by' exception to see the actual database/DDL error
  2. Grant the DB user the privileges needed by the save-mode (CREATE TABLE / DROP TABLE)
  3. Change the save-mode option (e.g. from create_when_not_exist/drop_and_create to error_if_schema_mismatch or reuse the existing table)
  4. Test the DDL manually against the target database

Example fix

// before
save_mode = "DROP_AND_CREATE"
// after
save_mode = "APPEND"  # or ERROR_IF_SCHEMA_MISMATCH if DDL privileges are missing
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-flight: check DDL privileges on the target database
// GRANT CREATE, DROP ON target_db.* TO 'seatunnel_user'@'%';

Try / catch

try {
    runJob();
} catch (SeaTunnelRuntimeException e) {
    if ("API-09".equals(((SeaTunnelRuntimeException) e).getSeaTunnelErrorCode().getCode())) {
        log.error("Save-mode DDL failed; check cause", e.getCause());
    }
}

Prevention

When it happens

Trigger: Sink implements SupportSaveMode and its SaveModeHandler.open() or execute() throws — e.g. CREATE TABLE fails on the target database (insufficient privileges, syntax unsupported, connection failure), or DROP/TRUNCATE of an existing table fails.

Common situations: Target database user lacks CREATE/DROP privileges; table exists with a conflicting schema; JDBC dialect in the save-mode handler not supporting the target DB version; network/credentials issues to the catalog DB.

Related errors


AI-assisted analysis of apache/seatunnel@cf67b549a7 (2026-09-10). Data as JSON: /api/errors/1cfe3bf58add8205. Report an issue: GitHub.

Appendix: source

Thrown at seatunnel-core/seatunnel-spark-starter/seatunnel-spark-starter-common/src/main/java/org/apache/seatunnel/core/starter/spark/execution/SinkExecuteProcessor.java:226

        if (createdAnySink && !skippedTables.isEmpty()) {
            log.warn(
                    MultiTableFailureHelper.formatFailedTableSummary(
                            "Some sink tables were skipped in Spark starter.", skippedTables));
        }
        // the sink is the last stream
        return null;
    }

    public void handleSaveMode(SeaTunnelSink sink) {
        if (sink instanceof SupportSaveMode) {
            Optional<SaveModeHandler> saveModeHandler =
                    ((SupportSaveMode) sink).getSaveModeHandler();
            if (saveModeHandler.isPresent()) {
                try (SaveModeHandler handler = saveModeHandler.get()) {
                    handler.open();
                    new SaveModeExecuteWrapper(handler).execute();
                } catch (Exception e) {
                    throw new SeaTunnelRuntimeException(HANDLE_SAVE_MODE_FAILED, e);
                }
            }
        }
    }

    private boolean shouldContinueOtherTables() {
        return MultiTableFailureHelper.shouldContinueOtherTables(
                ReadonlyConfig.fromConfig(sparkRuntimeEnvironment.getConfig()));
    }

    private RuntimeException wrapThrowable(Throwable error) {
        if (error instanceof RuntimeException) {
            return (RuntimeException) error;
        }
        return new RuntimeException(error);
    }

    private void logSkippedTable(

View on GitHub (pinned to cf67b549a7)