pentaho/pentaho-kettle · error · KettlePluginException

Unable to load class

Error message

Unable to load class: ${className}

What it means

When scanning a jar-based plugin, BasePluginType loads the plugin's main class with a URLClassLoader built over the plugin jar. If loadClass returns null (or otherwise fails to resolve), KettlePluginException 'Unable to load class: <className>' is thrown. This means the jar declares a plugin main class that cannot actually be loaded from that jar's classloader.

Solutions

  1. Verify the class named in the message actually exists in the plugin jar (jar tf plugin.jar | grep ClassName).
  2. Add the plugin's missing dependency jars to its lib folder or the Kettle classpath.
  3. Rebuild the plugin against your Kettle/JDK version, or run Kettle on the JDK version the plugin was compiled for.
  4. Replace the plugin jar with a complete, version-compatible build.

Example fix

// before
plugins/myplugin/
  myplugin.jar        // com.acme.Main missing (incomplete build)
// after
plugins/myplugin/
  myplugin.jar        // contains com.acme.Main
  lib/dependency.jar
Defensive patterns

Strategy: validation

Validate before calling

File jar = new File(jarPath);
try (java.util.jar.JarFile jf = new java.util.jar.JarFile(jar)) {
  if (jf.getJarEntry(className.replace('.', '/') + ".class") == null) {
    throw new IllegalStateException("Class missing from plugin jar: " + className);
  }
}

Type guard

static boolean classLoadable(String className, ClassLoader cl) {
  try { Class.forName(className, false, cl); return true; }
  catch (Throwable t) { return false; }
}

Try / catch

try {
  pluginType.searchPlugins();
} catch (KettlePluginException e) {
  log.logError("Plugin class could not be loaded: " + e.getMessage(), e);
}

Prevention

When it happens

Trigger: searchPlugins()/jar scanning -> new BasePluginType(...)/registerPluginJars where JarFilePlugin.getClassName() names a class absent from the jar, incompatible with the current JVM (bad bytecode version), or whose dependencies are missing so loadClass fails.

Common situations: Plugin jar built for a different Kettle version; incomplete plugin folder missing dependency jars; Java version mismatch (e.g. class compiled for Java 17 on Java 11 runtime); fat-jar shading that stripped plugin classes.

Related errors


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

Appendix: source

Thrown at core/src/main/java/org/pentaho/di/core/plugins/BasePluginType.java:849

   * in the lib directory of plugin folders.
   *
   * @param transverseLibDirs
   */
  protected void setTransverseLibDirs( boolean transverseLibDirs ) {
    this.searchLibDir = transverseLibDirs;
  }

  protected void registerPluginJars() throws KettlePluginException {
    List<JarFileAnnotationPlugin> jarFilePlugins = findAnnotatedClassFiles( pluginClass.getName() );
    for ( JarFileAnnotationPlugin jarFilePlugin : jarFilePlugins ) {

      URLClassLoader urlClassLoader =
        createUrlClassLoader( jarFilePlugin.getJarFile(), getClass().getClassLoader() );

      try {
        Class<?> clazz = urlClassLoader.loadClass( jarFilePlugin.getClassName() );
        if ( clazz == null ) {
          throw new KettlePluginException( "Unable to load class: " + jarFilePlugin.getClassName() );
        }
        List<String> libraries = Arrays.stream( urlClassLoader.getURLs() )
          .map( URL::getFile )
          .collect( Collectors.toList() );
        Annotation annotation = clazz.getAnnotation( pluginClass );

        handlePluginAnnotation( clazz, annotation, libraries, false, jarFilePlugin.getPluginFolder() );
      } catch ( Exception e ) {
        // Ignore for now, don't know if it's even possible.
        LogChannel.GENERAL.logError(
          "Unexpected error registering jar plugin file: " + jarFilePlugin.getJarFile(), e );
      } finally {
        if ( urlClassLoader instanceof KettleURLClassLoader ) {
          ( (KettleURLClassLoader) urlClassLoader ).closeClassLoader();
        }
      }
    }
  }

View on GitHub (pinned to f3058517a1)