jenkinsci/jenkins · error · IOException
{} doesn't extend from hudson.Plugin
Error message
{} doesn't extend from hudson.Plugin What it means
IOException thrown during plugin instantiation when the plugin class named in the manifest loads and instantiates successfully but the resulting object is not an instance of hudson.Plugin. Jenkins requires the declared plugin class to extend hudson.Plugin so its lifecycle can be managed.
Source
Thrown at core/src/main/java/hudson/ClassicPluginStrategy.java:387
}
@Override
public void load(PluginWrapper wrapper) throws IOException {
// override the context classloader. This no longer makes sense,
// but it is left for the backward compatibility
ClassLoader old = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(wrapper.classLoader);
try {
String className = wrapper.getPluginClass();
if (className == null) {
// use the default dummy instance
wrapper.setPlugin(new DummyImpl());
} else {
try {
Class<?> clazz = wrapper.classLoader.loadClass(className);
Object o = clazz.getDeclaredConstructor().newInstance();
if (!(o instanceof Plugin)) {
throw new IOException(className + " doesn't extend from hudson.Plugin");
}
wrapper.setPlugin((Plugin) o);
} catch (LinkageError | ClassNotFoundException e) {
throw new IOException("Unable to load " + className + " from " + wrapper.getShortName(), e);
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
throw new IOException("Unable to create instance of " + className + " from " + wrapper.getShortName(), e);
}
}
// initialize plugin
try {
Plugin plugin = wrapper.getPluginOrFail();
plugin.setServletContext(pluginManager.context);
startPlugin(wrapper);
} catch (Throwable t) {
// gracefully handle any error in plugin.
throw new IOException("Failed to initialize", t);
}View on GitHub (pinned to 2e228ff40b)
Solutions
- Make the class referenced by the manifest Plugin-Class extend hudson.Plugin (or remove the Plugin-Class attribute to use the default DummyImpl).
- If the plugin has no lifecycle need, delete the Plugin-Class entry from the manifest instead of pointing at a non-Plugin class.
- Rebuild and reinstall the plugin after the change.
Example fix
// before
public class MyPlugin {
...
}
// after
import hudson.Plugin;
public class MyPlugin extends Plugin {
...
} Defensive patterns
Strategy: type-guard
Validate before calling
Class<?> clazz = wrapper.classLoader.loadClass(className);
if (!Plugin.class.isAssignableFrom(clazz)) {
throw new IOException(className + " must extend hudson.Plugin");
} Type guard
boolean isPluginClass(Class<?> c) {
return c != null && hudson.Plugin.class.isAssignableFrom(c);
} Try / catch
try {
Object o = clazz.getDeclaredConstructor().newInstance();
if (!(o instanceof Plugin)) {
// instruct plugin author to extend hudson.Plugin
}
} catch (IOException e) {
// report the manifest Plugin-Class value
} Prevention
- Make the Plugin-Class extend hudson.Plugin, or omit the attribute.
- Add a unit test asserting the plugin class is a Plugin subclass.
- Review the manifest Plugin-Class after refactoring the main class.
When it happens
Trigger: A plugin manifest's Plugin-Class attribute points at a class that does not extend hudson.Plugin (e.g. a utility class, a Jelly-bound object, or a class extending jenkins.model.Jenkins by mistake).
Common situations: Plugin developer set Plugin-Class to the wrong class; refactored the main plugin class to no longer extend Plugin; copied a manifest from another plugin without updating Plugin-Class.
Related errors
- Failed to load {}. The file does not exist
- Failed to load {}. It is not a file
- Failed to load {}
- Plugin installation failed. No manifest at {}
- Refusing to load the Jenkins test harness in production (via
AI-assisted analysis of jenkinsci/jenkins@2e228ff40b (2026-08-14).
Data as JSON: /api/errors/c8ca9eaec6ed9be4.
Report an issue: GitHub.