halo-dev/halo · critical · PluginRuntimeException

Failed to load class %s for plugin %s.

Error message

Failed to load class %s for plugin %s.

What it means

Thrown as PluginRuntimeException during DefaultPluginApplicationContextFactory when classLoader.loadClass(className) throws ClassNotFoundException for one of the plugin's extension class names reported by pluginManager.getExtensionClassNames(pluginId). Halo tries to load each declared extension class while building the plugin's application context; a missing class aborts plugin startup.

Source

Thrown at application/src/main/java/run/halo/app/plugin/DefaultPluginApplicationContextFactory.java:156

        rootContext.getBeanProvider(PluginRouterFunctionRegistry.class).ifUnique(registry -> {
            var pluginRouterFunctionManager = new PluginRouterFunctionManager(registry);
            beanFactory.registerSingleton("pluginRouterFunctionManager", pluginRouterFunctionManager);
        });

        rootContext
                .getBeanProvider(SearchService.class)
                .ifUnique(searchService -> beanFactory.registerSingleton("searchService", searchService));

        sw.stop();

        sw.start("LoadComponents");
        var classNames = pluginManager.getExtensionClassNames(pluginId);
        classNames.stream()
                .map(className -> {
                    try {
                        return classLoader.loadClass(className);
                    } catch (ClassNotFoundException e) {
                        throw new PluginRuntimeException(String.format("""
                        Failed to load class %s for plugin %s.\
                        """, className, pluginId), e);
                    }
                })
                .forEach(clazzName -> context.registerBean(clazzName));
        sw.stop();
        log.debug("Created application context for plugin {}", pluginId);

        log.debug("Refreshing application context for plugin {}", pluginId);
        sw.start("Refresh");

        // Set the context ClassLoader to the plugin ClassLoader to ensure that
        // any class loading operations performed by the context (e.g., initializing
        // bean definitions, loading class resources during static initialization)
        // use the correct ClassLoader.
        var previous = Thread.currentThread().getContextClassLoader();
        try {
            Thread.currentThread().setContextClassLoader(classLoader);

View on GitHub (pinned to d2f5165f9c)

Solutions

  1. Rebuild the plugin against the currently installed Halo version and repackage.
  2. Ensure the extension manifest (class names) matches the actual classes in the JAR.
  3. Verify all required dependencies are shaded/packaged into the plugin JAR.
  4. Reinstall the plugin JAR cleanly (remove the old copy) and restart.

Example fix

# before
# plugin manifest lists run.example.OldExtension which was renamed

# after
# update plugin manifest to run.example.NewExtension and rebuild:
./gradlew clean build
# redeploy build/libs/*.jar
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate the plugin JAR's declared extension classes against its entries before install
for (String cn : pluginManager.getExtensionClassNames(pluginId)) {
    if (!jarEntries.contains(cn.replace('.', '/') + ".class")) {
        throw new IllegalStateException("Plugin declares missing class: " + cn);
    }
}

Try / catch

try {
    factory.createApplicationContext(pluginId);
} catch (PluginRuntimeException e) {
    log.error("Failed to start plugin {} (class load failed); reinstall against this Halo version", pluginId, e);
    disablePlugin(pluginId);
}

Prevention

When it happens

Trigger: A plugin JAR whose extension class list (declared via SPI/extension manifest) references a class not present in the plugin classloader: renamed/removed class, shaded relocation mismatch, dependency class not packaged, or version skew between plugin build and runtime API.

Common situations: Plugin rebuilt against a different Halo API version; refactored/renamed extension class not reflected in the manifest; missing optional dependency packaged only at compile time; corrupted/incomplete JAR; plugin built for an older/newer core than installed.

Related errors


AI-assisted analysis of halo-dev/halo@d2f5165f9c (2026-08-14). Data as JSON: /api/errors/9b3e61efd71e321e. Report an issue: GitHub.