apache/seatunnel · error · SeaTunnelRuntimeException

API-09

API-09

Error message

Handle save mode failed

What it means

SeaTunnelRuntimeException with code API-09 thrown by handleSaveMode when executing a sink's SaveModeHandler (auto table creation / schema evolution) throws any exception. The library wraps the original error to attribute it to the save-mode step so users know the sink never received data — pre-write DDL failed.

Source

Thrown at seatunnel-core/seatunnel-flink-starter/seatunnel-flink-13-starter/src/main/java/org/apache/seatunnel/core/starter/flink/execution/SinkExecuteProcessor.java:268

        }
        if (sinks.values().stream().anyMatch(sink -> !(sink instanceof SupportMultiTableSink))) {
            LOGGER.info("Unsupported multi table sink api, rollback to sink template");
            // choose the first sink
            return sinks.values().iterator().next();
        }
        return FactoryUtil.createMultiTableSink(sinks, sinkConfig, classLoader);
    }

    public void handleSaveMode(SeaTunnelSink seaTunnelSink) {
        if (seaTunnelSink instanceof SupportSaveMode) {
            SupportSaveMode saveModeSink = (SupportSaveMode) seaTunnelSink;
            Optional<SaveModeHandler> saveModeHandler = saveModeSink.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(envConfig));
    }

    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)

Solutions

  1. Inspect the wrapped cause 'e' for the actual database/DDL error
  2. Grant the DB user CREATE/ALTER privileges or pre-create the table manually and use save_mode accordingly
  3. Fix save_mode config (e.g. invalid custom DDL in save_mode templates) to match the target dialect

Example fix

# before
save_mode {
  custom_sql = "CREATE TABEL t (...)"  # typo
}
# after
save_mode {
  custom_sql = "CREATE TABLE t (...)"
}
Defensive patterns

Strategy: try-catch

Validate before calling

// check DDL privileges beforehand
try (Connection c = dataSource.getConnection()) {
    DatabaseMetaData md = c.getMetaData();
    boolean canCreate = !c.getMetaData().getTableTypes().next() == false; // probe via test DDL instead
}

Try / catch

try {
    sink.execute(...);
} catch (SeaTunnelRuntimeException e) {
    if ("API-09".equals(e.getSeaTunnelErrorCode().getCode())) {
        Throwable root = e.getCause(); // real DDL/DB failure
        log.error("Save mode failed: {}", root.getMessage());
    }
}

Prevention

When it happens

Trigger: A sink implementing SupportsSaveMode returns a SaveModeHandler; handler.open() or SaveModeExecuteWrapper.execute() fails — e.g. DDL statement rejected by the target database, insufficient privileges, or connection failure during table creation — inside handleSaveMode called from execute.

Common situations: Target database user lacks CREATE/ALTER privileges; schema-reflect DDL incompatible with the target DB dialect; network/credentials failure while opening the catalog connection.

Related errors


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