apache/pulsar · error · IOException

The '${offloaderName}' offloader does not provide an offload

Error message

The '${offloaderName}' offloader does not provide an offloader factory implementation

What it means

OffloaderUtils.getOffloaderFactory() reads the offloader's service definition YAML (META-INF services for PULSAR_OFFLOADER_SERVICE_NAME) and requires an offloaderFactoryClass entry. If the configured offloader plugin's definition lacks that field, an IOException is thrown. The driver jar is present but does not declare a factory implementation.

Source

Thrown at managed-ledger/src/main/java/org/apache/bookkeeper/mledger/offload/OffloaderUtils.java:66

     * @return the offloader class name
     * @throws IOException when fail to retrieve the pulsar offloader class
     */
    static Pair<NarClassLoader, LedgerOffloaderFactory<?>> getOffloaderFactory(String narPath,
                                                                              String narExtractionDirectory)
            throws IOException {
        // need to load offloader NAR to the classloader that also loaded LedgerOffloaderFactory in case
        // LedgerOffloaderFactory is loaded by a classloader that is not the default classloader
        NarClassLoader ncl = NarClassLoaderBuilder.builder()
                .narFile(new File(narPath))
                .parentClassLoader(LedgerOffloaderFactory.class.getClassLoader())
                .extractionDirectory(narExtractionDirectory)
                .build();
        String configStr = ncl.getServiceDefinition(PULSAR_OFFLOADER_SERVICE_NAME);

        OffloaderDefinition conf = ObjectMapperFactory.getYamlMapper().getObjectMapper()
            .readValue(configStr, OffloaderDefinition.class);
        if (StringUtils.isEmpty(conf.getOffloaderFactoryClass())) {
            throw new IOException(
                String.format("The '%s' offloader does not provide an offloader factory implementation",
                    conf.getName()));
        }

        try {
            // Try to load offloader factory class and check it implements Offloader interface
            Class<?> factoryClass = ncl.loadClass(conf.getOffloaderFactoryClass());
            CompletableFuture<LedgerOffloaderFactory<?>> loadFuture = new CompletableFuture<>();
            Thread loadingThread = new Thread(() -> {
                Thread.currentThread().setContextClassLoader(ncl);
                try {
                    Object offloader = factoryClass.getDeclaredConstructor().newInstance();
                    if (!(offloader instanceof LedgerOffloaderFactory)) {
                        throw new IOException("Class " + conf.getOffloaderFactoryClass() + " does not implement "
                                + "interface " + LedgerOffloaderFactory.class.getName());
                    }
                    loadFuture.complete((LedgerOffloaderFactory<?>) offloader);
                } catch (Throwable t) {

View on GitHub (pinned to 820761864e)

Solutions

  1. Replace the offloader jar in the ./offloaders directory with a version matching your broker that declares offloaderFactoryClass
  2. Update custom offloader plugins to implement LedgerOffloaderFactory and ship the OffloaderDefinition YAML
  3. Verify with: unzip -p driver.jar META-INF/services/...yaml (check offloaderFactoryClass is set)
  4. Ensure only one offloader definition for the same name is on the classpath — conflicts can load the wrong definition

Example fix

// before (offloader-definition.yaml in legacy driver)
name: my-s3
offloaderFactoryClass:   # missing
// after
name: my-s3
offloaderFactoryClass: org.example.MyS3OffloaderFactory
Defensive patterns

Strategy: validation

Validate before calling

// pre-check the NAR's service definition before configuring
try (ZipFile z = new ZipFile(offloaderNar)) {
    String yaml = readEntry(z, "META-INF/services/io.opensearch...yaml");
    OffloaderDefinition d = YAML_MAPPER.readValue(yaml, OffloaderDefinition.class);
    if (d.getOffloaderFactoryClass() == null || d.getOffloaderFactoryClass().isEmpty()) {
        throw new IllegalStateException("offloader jar lacks offloaderFactoryClass: " + offloaderNar);
    }
}

Try / catch

try {
    OffloaderUtils.getOffloaderFactory(ncl, conf, nrConf);
} catch (IOException e) {
    log.error("Offloader jar missing factory impl — replace jar with a broker-compatible version", e);
}

Prevention

When it happens

Trigger: Configuring a tiered-storage offloader whose driver jar contains an offloader definition YAML with an empty/missing offloaderFactoryClass — typically a legacy driver built for older Pulsar (pre-2.8) that used direct Offloader classes instead of OffloaderFactory, or a hand-rolled plugin missing the service definition.

Common situations: Upgrading Pulsar past 2.8 while keeping old offloader jars in ./offloaders; mixing driver versions (new driver jar with old, copied yaml); custom offloader plugin not updated to the factory SPI.

Related errors


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