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
- Inspect the wrapped cause 'e' for the actual database/DDL error
- Grant the DB user CREATE/ALTER privileges or pre-create the table manually and use save_mode accordingly
- 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
- Grant CREATE/ALTER privileges to the sink database user
- Dry-run save_mode custom SQL against the target database
- Pre-create target tables to minimize save-mode work
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
- API-09
- SQL_OPERATION_FAILED
- TABLE_QUERY_FAILED
- CONFIG_VALIDATION_FAILED
- All candidate sink tables were skipped in Spark starter.
AI-assisted analysis of apache/seatunnel@cf67b549a7 (2026-09-10).
Data as JSON: /api/errors/cbd2e3c1cf6310b9.
Report an issue: GitHub.