pentaho/pentaho-kettle · error · KettlePluginException

Unable to find class loader for plugin

Error message

Unable to find class loader for plugin: ${plugin}

What it means

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.

Solutions

  1. 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).
  2. Ensure the plugin's registration/init lifecycle runs before loadClass is invoked.
  3. If the plugin should use the registry's class-loader management instead, do not implement ClassLoadingPluginInterface and let the registry build a URLClassLoader.
  4. As a caller, check plugin instanceof ClassLoadingPluginInterface and verify the loader before calling loadClass.

Example fix

// before
@Override
public ClassLoader getClassLoader() { return classLoader; } // field never assigned -> null
// after
@Override
public ClassLoader getClassLoader() {
  if (classLoader == null) {
    classLoader = new URLClassLoader(jarUrls, getClass().getClassLoader());
  }
  return classLoader;
}
Defensive patterns

Strategy: type-guard

Validate before calling

if (plugin instanceof ClassLoadingPluginInterface) {
  if (((ClassLoadingPluginInterface) plugin).getClassLoader() == null) {
    throw new IllegalStateException("Plugin " + plugin + " has no class loader; fix its getClassLoader()");
  }
}

Type guard

boolean hasClassLoader(PluginInterface plugin) {
  return !(plugin instanceof ClassLoadingPluginInterface)
      || ((ClassLoadingPluginInterface) plugin).getClassLoader() != null;
}

Try / catch

try {
  Class<T> c = PluginRegistry.getInstance().loadClass(plugin, className, clazz);
} catch (KettlePluginException e) {
  if (e.getMessage() != null && e.getMessage().contains("Unable to find class loader")) {
    LOG.error("Plugin " + plugin + " never initialized its ClassLoader", e);
  }
  throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of pentaho/pentaho-kettle@f3058517a1 (2026-09-13). Data as JSON: /api/errors/afe4cdb754dada3f. Report an issue: GitHub.

Appendix: source

Thrown at core/src/main/java/org/pentaho/di/core/plugins/PluginRegistry.java:851

  /**
   * Load the class with a certain name using the class loader of certain plugin.
   *
   * @param plugin    The plugin for which we want to use the class loader
   * @param className The name of the class to load
   * @return the name of the class
   * @throws KettlePluginException In case there is something wrong
   */
  @SuppressWarnings( "unchecked" )
  public <T> T getClass( PluginInterface plugin, String className ) throws KettlePluginException {
    try {
      if ( plugin.isNativePlugin() ) {
        return (T) Class.forName( className );
      } else if ( plugin instanceof ClassLoadingPluginInterface ) {
        ClassLoadingPluginInterface cpi = (ClassLoadingPluginInterface) plugin;
        ClassLoader cl = cpi.getClassLoader();
        if ( cl == null ) {
          throw new KettlePluginException( "Unable to find class loader for plugin: " + plugin );
        }
        return (T) cl.loadClass( className );
      } else {
        URLClassLoader ucl;
        lock.writeLock().lock();
        try {
          Map<PluginInterface, URLClassLoader> classLoaders =
            classLoaderMap.computeIfAbsent( plugin.getPluginType(), k -> new HashMap<>() );
          ucl = classLoaders.get( plugin );

          if ( ucl == null ) {
            if ( !Utils.isEmpty( plugin.getClassLoaderGroup() ) ) {
              ucl = classLoaderGroupsMap.get( plugin.getClassLoaderGroup() );
            } else {
              ucl = folderBasedClassLoaderMap.get( plugin.getPluginDirectory().toString() );
            }
          }
          if ( ucl != null ) {

View on GitHub (pinned to f3058517a1)