I am trying to compile, load and use an inherited class at runtime. Here is the structure of my Java/Maven project. Note there are no .java files under com.mycompany.model.inherited. This is where generated class files will be placed.
src/main/java
com.mycompany.model.base
BaseClass.java
src/test/java
com.mycompany.model.inherited
BaseClass.java
package com.mycompany.model.base;
public abstract class BaseClass {
public abstract void doWork();
}
At runtime I write the contents of an inherited class to src/test/java/com/mycompany/inherited/SubClass.java
package com.mycompany.model.inherited;
import com.mycompany.model.BaseClass;
public class SubClass extends BaseClass {
@Override
public void doWork() {
System.out.println("SubClass is doing work.");
}
}
And then I execute this code to compile SubClass.java, load it, create an object instance. All of that works fine. But when I try to cast it to a BaseClass I'm getting a ClassCastException at runtime. Here's the full code.
// prepare the class file contents and write it to disk.
String subClassContents =
"package com.mycompany.model.inherited;\n" +
"\n" +
"import com.mycompany.model.base.BaseClass;\n" +
"\n" +
"public class SubClass extends BaseClass {\n" +
"\n" +
" @Override\n" +
" public void doWork() {\n" +
" System.out.println(\"SubClass is doing work.\");\n" +
" }\n" +
"}\n";
File file = new File(System.getProperty("user.dir") + "\\src\\test\\java\\com\\mycompany\\model\\inherited\\SubClass.java");
FileWriter writer = new FileWriter(file);
writer.write(subClassContents);
writer.close();
// compile the class using the current classpath.
File[] files = new File[]{file};
List<String> options = new ArrayList<>();
options.addAll(Arrays.asList("-classpath", System.getProperty("java.class.path")));
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager filemanager = compiler.getStandardFileManager(null, null, null);
Iterable fileObjects = filemanager.getJavaFileObjects(files);
JavaCompiler.CompilationTask task = compiler.getTask(null, null, null, options, null, fileObjects);
task.call();
// load the class using the current classpath.
String[] paths = System.getProperty("java.class.path").split(";");
List<URL> urls = new ArrayList<>();
urls.add(new File("src/test/java").toURI().toURL());
for (String p : paths) {
urls.add(new File(p).toURI().toURL());
}
URLClassLoader classLoader = URLClassLoader.newInstance(urls.stream().toArray(URL[]::new));
Class<?> cls = Class.forName("com.mycompany.model.inherited.SubClass", true, classLoader);
// use the class.
BaseClass base = (BaseClass) cls.newInstance();
base.doWork();
java.lang.ClassCastException: com.mycompany.model.inherited.SubClass cannot be cast to com.mycompany.model.base.BaseClass
If I include src/test/java/com/mycompany/model/inherited/SubClass.java at compile time it works fine. I.e:
BaseClass base = (BaseClass) SubClass.class.getClassLoader().loadClass("com.mycompany.model.inherited.SubClass").newInstance();
base.doWork();
Hoping someone can point to the issue and help me solve it. Thanks!
The cast failed because the base class of
SubClassis actually considered a different class from the "BaseClass" that you wrote in your source code. They are considered different because they are loaded by different class loaders. The base class ofSubClassis loaded by theURLClassLoaderyou created, whereas theBaseClassyou wrote in the source code is presumably loaded by the system class loader.To fix this, you can set the parent class loader of the
URLClassLoaderto the same class loader as the one that loadedBaseClass, and only givesrc/test/javaas the class path. This way, when theURLClassLoaderloadsSubClass, it can't findBaseClassinsrc/test/javaand so would use the parent class loader to load it.