pentaho/pentaho-kettle · error · KettlePluginClassMapException

PLUGINREGISTRY002

PLUGINREGISTRY002

Error message

Plugin "{0}" is unable to load class {1}

What it means

In PluginRegistry.loadClass, when the plugin implements ClassLoadingPluginInterface, the class comes from plugin.loadClass(pluginClass). If that returns null, a KettlePluginClassMapException with key ...NoValidClassRequested.PLUGINREGISTRY002 ('Plugin "X" is unable to load class Y') is thrown. It means the plugin is registered but its class map holds no class assignable to the requested interface.

Solutions

  1. Request a class type the plugin actually provides: match pluginClass to the plugin's kind (StepMetaInterface vs JobEntryInterface).
  2. Check the plugin's annotation/plugin.xml declares the class(es) expected to be mapped (classmappings).
  3. Inspect the plugin's registered class map via the registry to see which interfaces it supports.
  4. If using custom classloader groups, ensure the plugin was registered into the group the loader checks.

Example fix

// before
PluginInterface plugin = registry.findPluginWithId(JobEntryPluginType.class, "SHELL");
StepMetaInterface meta = registry.loadClass(plugin, StepMetaInterface.class); // wrong type

// after
PluginInterface plugin = registry.findPluginWithId(JobEntryPluginType.class, "SHELL");
JobEntryInterface jobEntry = registry.loadClass(plugin, JobEntryInterface.class);
Defensive patterns

Strategy: try-catch

Validate before calling

// Only request class types the plugin declares
if (plugin instanceof ClassLoadingPluginInterface) {
  Class<?>[] supported = ((ClassLoadingPluginInterface) plugin).getPluginClasses(); // if exposed
  // or consult registry class map before requesting
}

Type guard

static <T> T loadIfSupported(PluginRegistry reg, PluginInterface p, Class<T> cls) throws KettlePluginException {
  T o = reg.loadClass(p, cls); // call only after matching plugin kind to class type
  return o;
}

Try / catch

try {
  return PluginRegistry.getInstance().loadClass(plugin, pluginClass);
} catch (KettlePluginClassMapException e) {
  throw new KettleException("Plugin " + (plugin != null ? plugin.getName() : "?")
      + " does not map class " + pluginClass.getName(), e);
}

Prevention

When it happens

Trigger: loadClass is called with a ClassLoadingPluginInterface plugin whose internal class map has no entry (or a null entry) for the requested pluginClass type — e.g. requesting StepMetaInterface from a plugin that only registered a dialog/main class, or the plugin registered zero matching classes.

Common situations: Requesting the wrong interface type for the plugin kind (loading a JobEntry class from a step plugin); plugin annotation/manifest lists no matching class entries; plugin.xml main-class entries filtered out at scan; ClassLoadingPluginInterface.loadClass returns null for an unregistered class loader group.

Related errors


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

Appendix: source

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

  /**
   * Load and instantiate the plugin class specified
   *
   * @param plugin      the plugin to load
   * @param pluginClass the class to be loaded
   * @return The instantiated class
   * @throws KettlePluginException In case there was a class loading problem somehow
   */
  @SuppressWarnings( "unchecked" )
  public <T> T loadClass( PluginInterface plugin, Class<T> pluginClass ) throws KettlePluginException {
    if ( plugin == null ) {
      throw new KettlePluginException( BaseMessages.getString(
          PKG, "PluginRegistry.RuntimeError.NoValidStepOrPlugin.PLUGINREGISTRY001" ) );
    }

    if ( plugin instanceof ClassLoadingPluginInterface ) {
      T aClass = ( (ClassLoadingPluginInterface) plugin ).loadClass( pluginClass );
      if ( aClass == null ) {
        throw new KettlePluginClassMapException( BaseMessages
            .getString( PKG, "PluginRegistry.RuntimeError.NoValidClassRequested.PLUGINREGISTRY002", plugin.getName(),
                pluginClass.getName() ) );
      } else {
        return aClass;
      }
    } else {
      String className = plugin.getClassMap().get( pluginClass );
      if ( className == null ) {
        // Look for supplemental plugin supplying extra classes
        for ( String id : plugin.getIds() ) {
          try {
            T aClass = loadClass( plugin.getPluginType(), createSupplemantalKey( plugin.getPluginType().getName(), id ), pluginClass );
            if ( aClass != null ) {
              return aClass;
            }
          } catch ( KettlePluginException exception ) {
            // ignore. we'll fall through to the other exception if this loop doesn't produce a return
          }

View on GitHub (pinned to f3058517a1)