apache/pulsar · error · IllegalArgumentException

Could not find sink config class

Error message

Could not find sink config class

What it means

Thrown by SinkConfigUtils.validateAndExtractDetails when the connector definition declares a sinkConfigClass that cannot be loaded via Class.forName using the sink function's classloader (ClassNotFoundException is rewrapped as IllegalArgumentException). The connector's custom config class is required to validate the sink's user-provided configs.

Solutions

  1. Ensure the connector NAR contains the declared sinkConfigClass and matches the deployed version
  2. Correct the sinkConfigClass name in the connector definition (META-INF/services/connector yaml)
  3. Check the sinkFunction classloader includes the connector's jar; restart with the correct NAR

Example fix

// connector definition yaml
// before
sinkConfigClass: com.example.OldSinkConfig
// after
sinkConfigClass: com.example.MySinkConfig // class actually packaged in the NAR
Defensive patterns

Strategy: try-catch

Validate before calling

try {
    Class.forName(sinkConfigClassFqcn, true, fnClassLoader);
} catch (ClassNotFoundException e) {
    throw new IllegalStateException("Connector NAR is missing config class " + sinkConfigClassFqcn);
}

Try / catch

try {
    admin.sinks().updateSink(tenant, namespace, cfg, null);
} catch (IllegalArgumentException e) {
    if (e.getMessage().contains("Could not find sink config class")) { /* redeploy correct connector NAR */ }
    else throw e;
}

Prevention

When it happens

Trigger: Updating/submitting a sink backed by a connector whose ConnectorDefinition.getSinkConfigClass() names a class missing from the classpath/NAR, e.g. wrong NAR file version or class renamed in the connector.

Common situations: Custom connector NAR without the config class packaged; connector upgraded and config class renamed while old configs reference the old FQCN; typo in the sinkConfigClass declared in the connector's yaml.

Related errors


AI-assisted analysis of apache/pulsar@820761864e (2026-09-06). Data as JSON: /api/errors/96d2fef3322c80c1. Report an issue: GitHub.

Appendix: source

Thrown at pulsar-functions/utils/src/main/java/org/apache/pulsar/functions/utils/SinkConfigUtils.java:752

        }
        if (newConfig.getTransformFunctionConfig() != null) {
            mergedConfig.setTransformFunctionConfig(newConfig.getTransformFunctionConfig());
        }
        if (newConfig.getSourceSubscriptionPosition() != null) {
            mergedConfig.setSourceSubscriptionPosition(newConfig.getSourceSubscriptionPosition());
        }
        return mergedConfig;
    }

    public static void validateSinkConfig(SinkConfig sinkConfig, ValidatableFunctionPackage sinkFunction) {
        try {
            ConnectorDefinition defn = sinkFunction.getFunctionMetaData(ConnectorDefinition.class);
            if (defn != null && defn.getSinkConfigClass() != null) {
                Class<?> configClass = Class.forName(defn.getSinkConfigClass(), true, sinkFunction.getClassLoader());
                validateSinkConfig(sinkConfig, configClass);
            }
        } catch (ClassNotFoundException e) {
            throw new IllegalArgumentException("Could not find sink config class", e);
        }
    }

    public static void validateSinkConfig(SinkConfig sinkConfig, Class<?> configClass) {
        try {
            Object configObject =
                    ObjectMapperFactory.getMapper().getObjectMapper()
                            .convertValue(sinkConfig.getConfigs(), configClass);
            if (configObject != null) {
                ConfigValidation.validateConfig(configObject);
            }
        } catch (IllegalArgumentException e) {
            throw new IllegalArgumentException("Could not validate sink config: " + e.getMessage());
        }
    }
}

View on GitHub (pinned to 820761864e)