apache/seatunnel · error · JobDefineCheckException

The transform/sink(%s) is not configured with '%s' option

Error message

The transform/sink(%s) is not configured with '%s' option

What it means

checkExistInputTableId requires every transform/sink in a complex graph to declare a plugin_input id. If absent, JobDefineCheckException wrapping an OptionValidationException for PLUGIN_INPUT is thrown. Consumers in a multi-table DAG must state which upstream table they read.

Source

Thrown at seatunnel-engine/seatunnel-engine-core/src/main/java/org/apache/seatunnel/engine/core/parse/ConfigParserUtil.java:247

                });
    }

    private static void checkExistTableId(List<? extends Config> configs) {
        for (Config config : configs) {
            if (!ReadonlyConfig.fromConfig(config).getOptional(PLUGIN_OUTPUT).isPresent()) {
                throw new JobDefineCheckException(
                        String.format(
                                "The source/transform(%s) is not configured with '%s' option",
                                config.getString(PLUGIN_NAME.key()), PLUGIN_OUTPUT.key()),
                        new OptionValidationException(PLUGIN_OUTPUT));
            }
        }
    }

    private static void checkExistInputTableId(List<? extends Config> configs) {
        for (Config config : configs) {
            if (!ReadonlyConfig.fromConfig(config).getOptional(PLUGIN_INPUT).isPresent()) {
                throw new JobDefineCheckException(
                        String.format(
                                "The transform/sink(%s) is not configured with '%s' option",
                                config.getString(PLUGIN_NAME.key()), PLUGIN_INPUT.key()),
                        new OptionValidationException(PLUGIN_INPUT));
            }
        }
    }

    private static String getTableId(ReadonlyConfig config) {
        return config.getOptional(PLUGIN_OUTPUT).orElse(DEFAULT_ID);
    }

    static List<String> getInputIds(ReadonlyConfig config) {
        return config.getOptional(PLUGIN_INPUT).orElse(Collections.singletonList(DEFAULT_ID));
    }

    public static String getFactoryId(ReadonlyConfig readonlyConfig) {
        String pluginName = readonlyConfig.get(PLUGIN_NAME);

View on GitHub (pinned to cf67b549a7)

Solutions

  1. Add plugin_input = "existing-table-id" to every transform and sink block
  2. Alternatively use the legacy source_table_name option if supported
  3. Ensure the referenced id matches a plugin_output of an upstream plugin
  4. Check the plugin name in the message to locate the offending block

Example fix

// before
sink {
  Console {}
}
// after
sink {
  Console { plugin_input = "fake" }
}
Defensive patterns

Strategy: validation

Validate before calling

for (Config c : transformsAndSinks) {
    if (!c.hasPath("plugin_input") && !c.hasPath("source_table_name")) {
        throw new IllegalArgumentException("Missing plugin_input for " + c.getString("plugin_name"));
    }
}

Try / catch

try {
    ConfigParserUtil.checkExistInputTableId(configs);
} catch (JobDefineCheckException e) {
    log.error("Missing plugin_input: {}", e.getMessage());
    throw e;
}

Prevention

When it happens

Trigger: A transform or sink block in a complex multi-table job omits plugin_input (or source_table_name); enforced by checkExistInputTableId inside checkComplexGraph.

Common situations: Simple single-table config (where plugin_input is optional) grew into a multi-table graph by adding a second source; plugin blocks copied without adding input ids; ambiguous consumer after renaming tables.

Understand the failure class

Background: "Must pass :limit option" / "Missing required option" — required option errors explained — this error's family across 41 libraries.

Related errors


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