apache/seatunnel · error · FactoryException

sinkOptionRule can not be null

Error message

sinkOptionRule can not be null

What it means

FactoryUtil.sinkFullOptionRule throws this FactoryException when the given TableSinkFactory's optionRule() returns null. Like the source variant, a null rule means the factory does not satisfy the API contract of exposing its configuration options.

Solutions

  1. Implement optionRule() in the TableSinkFactory returning a non-null OptionRule.
  2. Upgrade the sink connector to a release that declares its option rule.
  3. Return an empty OptionRule if the sink truly has no user options.

Example fix

// before
@Override
public OptionRule optionRule() { return null; }
// after
@Override
public OptionRule optionRule() {
    return OptionRule.builder().required(SinkOptions.JDBC_URL).build();
}
Defensive patterns

Strategy: type-guard

Validate before calling

OptionRule rule = factory.optionRule();
if (rule == null) {
    throw new IllegalStateException(factory.factoryIdentifier() + " must implement optionRule()");
}

Type guard

boolean hasValidOptionRule(TableSinkFactory f) { return f.optionRule() != null; }

Try / catch

try {
    return FactoryUtil.sinkFullOptionRule(factory);
} catch (FactoryException e) {
    throw new IllegalStateException("Sink connector must declare a non-null OptionRule", e);
}

Prevention

When it happens

Trigger: Calling FactoryUtil.sinkFullOptionRule(factory) with a sink factory whose optionRule() is unimplemented or explicitly returns null — typical of custom or legacy connectors.

Common situations: Custom sink connector under development missing optionRule(); third-party sink jar built against an older API; copying a sink skeleton that left optionRule() returning null.

Related errors


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

Appendix: source

Thrown at seatunnel-api/src/main/java/org/apache/seatunnel/api/table/factory/FactoryUtil.java:447

            OptionRule sourceCommonOptionRule =
                    OptionRule.builder().optional(EnvCommonOptions.PARALLELISM).build();
            sourceOptionRule
                    .getOptionalOptions()
                    .addAll(sourceCommonOptionRule.getOptionalOptions());
        }

        return sourceOptionRule;
    }

    /**
     * This method is called by SeaTunnel Web to get the full option rule of a sink.
     *
     * @return Option rule
     */
    public static OptionRule sinkFullOptionRule(@NonNull TableSinkFactory factory) {
        OptionRule sinkOptionRule = factory.optionRule();
        if (sinkOptionRule == null) {
            throw new FactoryException("sinkOptionRule can not be null");
        }
        return sinkOptionRule;
    }

    public static SeaTunnelTransform<?> createAndPrepareMultiTableTransform(
            List<CatalogTable> catalogTables,
            ReadonlyConfig options,
            ClassLoader classLoader,
            String factoryIdentifier) {
        final TableTransformFactory factory =
                discoverFactory(classLoader, TableTransformFactory.class, factoryIdentifier);
        TableTransformFactoryContext context =
                new TableTransformFactoryContext(catalogTables, options, classLoader);
        ConfigValidator.of(context.getOptions()).validate(factory.optionRule());
        return factory.createTransform(context).createTransform();
    }

    private static <T extends Factory> boolean isFallback(

View on GitHub (pinned to cf67b549a7)