{"record":{"id":"afe4cdb754dada3f","repo":"pentaho/pentaho-kettle","slug":"unable-to-find-class-loader-for-plugin-plugin","errorCode":null,"errorMessage":"Unable to find class loader for plugin: ${plugin}","messagePattern":"Unable to find class loader for plugin: (.+?)","errorType":"exception","errorClass":"KettlePluginException","httpStatus":null,"severity":"error","filePath":"core/src/main/java/org/pentaho/di/core/plugins/PluginRegistry.java","lineNumber":851,"sourceCode":"\n  /**\n   * Load the class with a certain name using the class loader of certain plugin.\n   *\n   * @param plugin    The plugin for which we want to use the class loader\n   * @param className The name of the class to load\n   * @return the name of the class\n   * @throws KettlePluginException In case there is something wrong\n   */\n  @SuppressWarnings( \"unchecked\" )\n  public <T> T getClass( PluginInterface plugin, String className ) throws KettlePluginException {\n    try {\n      if ( plugin.isNativePlugin() ) {\n        return (T) Class.forName( className );\n      } else if ( plugin instanceof ClassLoadingPluginInterface ) {\n        ClassLoadingPluginInterface cpi = (ClassLoadingPluginInterface) plugin;\n        ClassLoader cl = cpi.getClassLoader();\n        if ( cl == null ) {\n          throw new KettlePluginException( \"Unable to find class loader for plugin: \" + plugin );\n        }\n        return (T) cl.loadClass( className );\n      } else {\n        URLClassLoader ucl;\n        lock.writeLock().lock();\n        try {\n          Map<PluginInterface, URLClassLoader> classLoaders =\n            classLoaderMap.computeIfAbsent( plugin.getPluginType(), k -> new HashMap<>() );\n          ucl = classLoaders.get( plugin );\n\n          if ( ucl == null ) {\n            if ( !Utils.isEmpty( plugin.getClassLoaderGroup() ) ) {\n              ucl = classLoaderGroupsMap.get( plugin.getClassLoaderGroup() );\n            } else {\n              ucl = folderBasedClassLoaderMap.get( plugin.getPluginDirectory().toString() );\n            }\n          }\n          if ( ucl != null ) {","sourceCodeStart":833,"sourceCodeEnd":869,"githubUrl":"https://github.com/pentaho/pentaho-kettle/blob/f3058517a153da500bf4551f46d79b91bf8ec552/core/src/main/java/org/pentaho/di/core/plugins/PluginRegistry.java#L833-L869","documentation":"In PluginRegistry.loadClassFromAnnotation or its class-loading helper, when a plugin implements ClassLoadingPluginInterface but cpi.getClassLoader() returns null, Kettle has no ClassLoader to load the requested class and throws this KettlePluginException. It means the plugin type is responsible for supplying its own class loader but never initialized one.","triggerScenarios":"Calling loadClass(PluginInterface plugin, String className) for a plugin that is not native and implements ClassLoadingPluginInterface, while the plugin's getClassLoader() returns null — i.e. the plugin's own class-loading hook was never set up.","commonSituations":"A custom ClassLoadingPluginInterface implementation forgot to create/return its class loader in getClassLoader(); a plugin was constructed through an unusual code path that skips loader initialization; plugin lifecycle methods (init/registration) were never called before class loading.","solutions":["Fix the plugin implementation so getClassLoader() always returns a non-null ClassLoader (e.g. this.getClass().getClassLoader() or a URLClassLoader over the plugin's jar URLs).","Ensure the plugin's registration/init lifecycle runs before loadClass is invoked.","If the plugin should use the registry's class-loader management instead, do not implement ClassLoadingPluginInterface and let the registry build a URLClassLoader.","As a caller, check plugin instanceof ClassLoadingPluginInterface and verify the loader before calling loadClass."],"exampleFix":"// before\n@Override\npublic ClassLoader getClassLoader() { return classLoader; } // field never assigned -> null\n// after\n@Override\npublic ClassLoader getClassLoader() {\n  if (classLoader == null) {\n    classLoader = new URLClassLoader(jarUrls, getClass().getClassLoader());\n  }\n  return classLoader;\n}","handlingStrategy":"type-guard","validationCode":"if (plugin instanceof ClassLoadingPluginInterface) {\n  if (((ClassLoadingPluginInterface) plugin).getClassLoader() == null) {\n    throw new IllegalStateException(\"Plugin \" + plugin + \" has no class loader; fix its getClassLoader()\");\n  }\n}","typeGuard":"boolean hasClassLoader(PluginInterface plugin) {\n  return !(plugin instanceof ClassLoadingPluginInterface)\n      || ((ClassLoadingPluginInterface) plugin).getClassLoader() != null;\n}","tryCatchPattern":"try {\n  Class<T> c = PluginRegistry.getInstance().loadClass(plugin, className, clazz);\n} catch (KettlePluginException e) {\n  if (e.getMessage() != null && e.getMessage().contains(\"Unable to find class loader\")) {\n    LOG.error(\"Plugin \" + plugin + \" never initialized its ClassLoader\", e);\n  }\n  throw e;\n}","preventionTips":["Implement ClassLoadingPluginInterface.getClassLoader() to lazily create a loader instead of returning a possibly-null field.","Call plugin lifecycle/registration methods before any loadClass usage.","Prefer the registry-managed loader path unless you truly need a custom ClassLoadingPluginInterface."],"tags":["plugin-loading","classloader","null-pointer","kettle"],"backgroundTag":"class-not-found","analyzedSha":"f3058517a153da500bf4551f46d79b91bf8ec552","analyzedAt":"2026-09-13T14:04:16.340Z","contentChangedAt":"2026-09-13T14:04:16.340Z","schemaVersion":2},"datasetVersion":"2026-09-20T23:17:15.980Z"}