{"record":{"id":"3213be0c1d99bfff","repo":"apache/pulsar","slug":"the-offloadername-offloader-does-not-provide","errorCode":null,"errorMessage":"The '${offloaderName}' offloader does not provide an offloader factory implementation","messagePattern":"The '(.+?)' offloader does not provide an offloader factory implementation","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"managed-ledger/src/main/java/org/apache/bookkeeper/mledger/offload/OffloaderUtils.java","lineNumber":66,"sourceCode":"     * @return the offloader class name\n     * @throws IOException when fail to retrieve the pulsar offloader class\n     */\n    static Pair<NarClassLoader, LedgerOffloaderFactory<?>> getOffloaderFactory(String narPath,\n                                                                              String narExtractionDirectory)\n            throws IOException {\n        // need to load offloader NAR to the classloader that also loaded LedgerOffloaderFactory in case\n        // LedgerOffloaderFactory is loaded by a classloader that is not the default classloader\n        NarClassLoader ncl = NarClassLoaderBuilder.builder()\n                .narFile(new File(narPath))\n                .parentClassLoader(LedgerOffloaderFactory.class.getClassLoader())\n                .extractionDirectory(narExtractionDirectory)\n                .build();\n        String configStr = ncl.getServiceDefinition(PULSAR_OFFLOADER_SERVICE_NAME);\n\n        OffloaderDefinition conf = ObjectMapperFactory.getYamlMapper().getObjectMapper()\n            .readValue(configStr, OffloaderDefinition.class);\n        if (StringUtils.isEmpty(conf.getOffloaderFactoryClass())) {\n            throw new IOException(\n                String.format(\"The '%s' offloader does not provide an offloader factory implementation\",\n                    conf.getName()));\n        }\n\n        try {\n            // Try to load offloader factory class and check it implements Offloader interface\n            Class<?> factoryClass = ncl.loadClass(conf.getOffloaderFactoryClass());\n            CompletableFuture<LedgerOffloaderFactory<?>> loadFuture = new CompletableFuture<>();\n            Thread loadingThread = new Thread(() -> {\n                Thread.currentThread().setContextClassLoader(ncl);\n                try {\n                    Object offloader = factoryClass.getDeclaredConstructor().newInstance();\n                    if (!(offloader instanceof LedgerOffloaderFactory)) {\n                        throw new IOException(\"Class \" + conf.getOffloaderFactoryClass() + \" does not implement \"\n                                + \"interface \" + LedgerOffloaderFactory.class.getName());\n                    }\n                    loadFuture.complete((LedgerOffloaderFactory<?>) offloader);\n                } catch (Throwable t) {","sourceCodeStart":48,"sourceCodeEnd":84,"githubUrl":"https://github.com/apache/pulsar/blob/820761864ed8e2a7d2e52dd9763ad2ae117c1395/managed-ledger/src/main/java/org/apache/bookkeeper/mledger/offload/OffloaderUtils.java#L48-L84","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Replace the offloader jar in the ./offloaders directory with a version matching your broker that declares offloaderFactoryClass","Update custom offloader plugins to implement LedgerOffloaderFactory and ship the OffloaderDefinition YAML","Verify with: unzip -p driver.jar META-INF/services/...yaml (check offloaderFactoryClass is set)","Ensure only one offloader definition for the same name is on the classpath — conflicts can load the wrong definition"],"exampleFix":"// before (offloader-definition.yaml in legacy driver)\nname: my-s3\noffloaderFactoryClass:   # missing\n// after\nname: my-s3\noffloaderFactoryClass: org.example.MyS3OffloaderFactory","handlingStrategy":"validation","validationCode":"// pre-check the NAR's service definition before configuring\ntry (ZipFile z = new ZipFile(offloaderNar)) {\n    String yaml = readEntry(z, \"META-INF/services/io.opensearch...yaml\");\n    OffloaderDefinition d = YAML_MAPPER.readValue(yaml, OffloaderDefinition.class);\n    if (d.getOffloaderFactoryClass() == null || d.getOffloaderFactoryClass().isEmpty()) {\n        throw new IllegalStateException(\"offloader jar lacks offloaderFactoryClass: \" + offloaderNar);\n    }\n}","typeGuard":null,"tryCatchPattern":"try {\n    OffloaderUtils.getOffloaderFactory(ncl, conf, nrConf);\n} catch (IOException e) {\n    log.error(\"Offloader jar missing factory impl — replace jar with a broker-compatible version\", e);\n}","preventionTips":["Keep ./offloaders jars at the same version as the broker","After Pulsar upgrades, refresh all offloader plugins","Inspect driver jar service definitions before deployment","For custom plugins, implement LedgerOffloaderFactory and ship OffloaderDefinition YAML"],"tags":["offload","plugin","configuration","tiered-storage","spi"],"backgroundTag":"missing-spi-implementation","analyzedSha":"820761864ed8e2a7d2e52dd9763ad2ae117c1395","analyzedAt":"2026-09-06T00:14:20.138Z","contentChangedAt":"2026-09-06T00:14:20.138Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}