Spring Boot Jar运行Weka报错:找不到weka.clusterers.Canopy类
Hey there, let's break down why you're hitting that Can't find a permissible class called: weka.clusterers.Canopy error when running your Spring Boot uber-jar, even though everything works smoothly in Eclipse.
The Root Cause
Spring Boot builds an uber-jar with nested JARs (your weka-stable jar is packaged inside the main Spring Boot jar). Eclipse uses a flat classpath setup, so its classloader can easily locate Weka classes. But when running the uber-jar, Spring Boot uses its own LaunchedURLClassLoader to load resources from nested JARs. The problem is that Weka's Utils.forName() method (used under the hood in AbstractClusterer.forName() and AddCluster.setOptions()) defaults to using the system classloader—which can't access classes inside nested JARs, hence the error even though the Canopy class is present.
Solutions to Try
1. Switch the Context ClassLoader Before Weka Code Runs
A quick, code-based fix is to set the current thread's context classloader to the one that loaded your application classes (which has access to nested Weka resources). Add this line right before your Weka-related logic executes:
// Place this in ClusterDetector.calculateClusterCentroids() or PredictiveController.getClusterCentroids() Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); // Then run your Weka code (like AddCluster.setOptions())
This tells Weka's classloading logic to use the correct classloader that can see the nested Weka classes.
2. Configure Spring Boot Maven Plugin to Unpack Weka JAR
If you prefer a configuration-only fix, you can tell the Spring Boot Maven plugin to unpack the Weka JAR into the uber-jar instead of nesting it. This makes Weka classes available to the system classloader directly.
Add this to your pom.xml:
<build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <requiresUnpack> <dependency> <groupId>nz.ac.waikato.cms.weka</groupId> <artifactId>weka-stable</artifactId> <version>3.8.1</version> <!-- Match your Weka version --> </dependency> </requiresUnpack> </configuration> </plugin> </plugins> </build>
Rebuild your uber-jar, and Weka's classes will be stored flat in the classes directory of the jar, making them visible to the system classloader.
3. Use Weka's Overloaded Utils.forName() Method (If Applicable)
If you're directly calling Utils.forName() in your own code, Weka offers an overload that lets you specify a custom classloader. Replace the default call with:
// Instead of Utils.forName("weka.clusterers.Canopy") Class<?> canopyClass = Utils.forName( "weka.clusterers.Canopy", getClass().getClassLoader(), null );
This bypasses the default classloader lookup and uses your application's classloader directly. Note: This only works if you're invoking Utils.forName() yourself—if the call is buried inside Weka's own code (like AddCluster.setOptions()), stick to solutions 1 or 2.
Which Solution to Choose?
- Start with Solution 1 for minimal code changes.
- Use Solution 2 if you prefer a configuration-only fix (it will make your uber-jar slightly larger, but no code tweaks needed).
- Go with Solution 3 only if you're directly calling
Utils.forName()in your codebase.
内容的提问来源于stack exchange,提问作者Vadim Yarovoy

