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
- 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.
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
- 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.
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
- PLUGINREGISTRY007
- PurRepository.LoginException.Message
- ScriptValuesMetaMod.Exception.UnableToLoadAdditionalClass
- Unable to load class
- Unexpected error loading class with name
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)