{"record":{"id":"4ac8fc39558a7bc1","repo":"apache/seatunnel","slug":"at-least-one-sink-plugin-must-be-configured","errorCode":null,"errorMessage":"At least one sink plugin must be configured.","messagePattern":"At least one sink plugin must be configured\\.","errorType":"validation","errorClass":"ConfigCheckException","httpStatus":null,"severity":"error","filePath":"seatunnel-core/seatunnel-core-starter/src/main/java/org/apache/seatunnel/core/starter/utils/ConfigValidationUtils.java","lineNumber":336,"sourceCode":"                    .validate(factory.optionRule());\n            SeaTunnelTransform<?> transform = factory.createTransform(context).createTransform();\n            transform.setJobContext(jobContext);\n\n            String pluginOutputIdentifier =\n                    ReadonlyConfig.fromConfig(transformConfig).get(PLUGIN_OUTPUT);\n            outputTables.put(\n                    pluginOutputIdentifier,\n                    new TableInfo(transform.getProducedCatalogTables(), pluginOutputIdentifier));\n        }\n        return new ArrayList<>(outputTables.values());\n    }\n\n    private static void validateSinks(\n            List<TableInfo> upstreamTables,\n            List<? extends Config> sinkConfigs,\n            JobContext jobContext) {\n        if (sinkConfigs.isEmpty()) {\n            throw new ConfigCheckException(\"At least one sink plugin must be configured.\");\n        }\n\n        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();\n        SeaTunnelFactoryDiscovery factoryDiscovery =\n                new SeaTunnelFactoryDiscovery(TableSinkFactory.class, ADD_URL_TO_CLASSLOADER);\n        SeaTunnelSinkPluginDiscovery sinkPluginDiscovery =\n                new SeaTunnelSinkPluginDiscovery(ADD_URL_TO_CLASSLOADER);\n        Function<PluginIdentifier, SeaTunnelSink> fallbackCreateSink =\n                sinkPluginDiscovery::createPluginInstance;\n\n        TableInfo defaultInput = upstreamTables.get(upstreamTables.size() - 1);\n        for (Config sinkConfig : sinkConfigs) {\n            PluginIdentifier pluginIdentifier = getPluginIdentifier(sinkConfig, PluginType.SINK);\n            TableSinkFactory sinkFactory =\n                    (TableSinkFactory)\n                            factoryDiscovery\n                                    .createOptionalPluginInstance(pluginIdentifier)\n                                    .orElse(null);","sourceCodeStart":318,"sourceCodeEnd":354,"githubUrl":"https://github.com/apache/seatunnel/blob/cf67b549a7a6c35fa0beb12d83c62892427ea919/seatunnel-core/seatunnel-core-starter/src/main/java/org/apache/seatunnel/core/starter/utils/ConfigValidationUtils.java#L318-L354","documentation":"validateSinks requires the job config to declare at least one sink plugin; an empty sink list throws ConfigCheckException('At least one sink plugin must be configured.'). Every SeaTunnel job must write data somewhere.","triggerScenarios":"Job config has no 'sink' block, an empty sink list, or sink entries not recognized as configs by getConfigList (e.g. sink written as object instead of list).","commonSituations":"Testing source-only configs, deleting the sink section while trimming examples, or a typo turning the sink block into something TypesafeConfigUtils ignores.","solutions":["Add a sink { ... } block (e.g. Console {}) to the job config","Ensure 'sink' is a list-shaped block containing a valid plugin name","Confirm the sink plugin name is spelled correctly so it parses as a plugin config"],"exampleFix":"// before\nenv {...}\nsource { Fake {} }\n// after\nenv {...}\nsource { Fake {} }\nsink { Console {} }","handlingStrategy":"validation","validationCode":"boolean hasSink = config.hasPath(\"sink\") && !config.getConfigList(\"sink\").isEmpty(); if (!hasSink) throw new IllegalArgumentException(\"config must define a sink block\");","typeGuard":null,"tryCatchPattern":"try { ConfigValidationUtils.validate(config); } catch (ConfigCheckException e) { log.error(\"Job config invalid: {}\", e.getMessage()); }","preventionTips":["Always include at least Console {} sink in templates","Lint configs for source+sink presence","Never rename the reserved 'sink' key"],"tags":["config","sink","validation"],"backgroundTag":"missing-required-config-field","analyzedSha":"cf67b549a7a6c35fa0beb12d83c62892427ea919","analyzedAt":"2026-09-10T21:44:55.265Z","contentChangedAt":"2026-09-10T21:44:55.265Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}