1

I have a jar file which I runs fine with java -jar my-jar-file.jar.

But now I have to load the main class from another java program using reflection and when I do that I get a ClassNotFoundException: javax.mail.MessagingException.

The code I'm using to do that:

URLClassLoader loader = URLClassLoader.newInstance(new URL[]{new File("my-jar-file.jar").toURI().toURL()});
Class<?> serverClass = loader.loadClass("com.foo.bar.application.MyMain");

Method main = serverClass.getMethod("main", String[].class);
String[] params = new String[]{"-flag", "value"};
main.invoke(null, (Object) params);

The stacktrace is like this (removed details):

Exception in thread "main" java.lang.reflect.InvocationTargetException
Caused by: java.lang.NoClassDefFoundError: javax/mail/MessagingException
Caused by: java.lang.ClassNotFoundException: javax.mail.MessagingException

I also tried adding entries from System.getProperty("java.class.path") to the array of URLs I pass to class loader, but no luck as well.

This error seems reasonable to me as the jar file does not contain javax.mail.MessagingException (does not contain javax.mail package actually) but how does it work when I just do java -jar my-jar-file.jar?

How can I fix this issue? Thanks

edit:

Added MANIFEST.MF contents:

Manifest-Version: 1.0
Main-Class: com.foo.bar.application.MyMain
Implementation-Version: 1.5.4

edit 2:

One more thing worth to mention is that the jar file contains a javamail.providers file in META-INF directory. Suppose this somehow makes that jar to work, but how can I do the same programmatically?

javamail.providers contents:

# AWS Mail Provider
protocol=aws; type=transport; class=com.amazonaws.services.simpleemail.AWSJavaMailTransport; vendor=Amazon Web Services LLC;
serejja
  • 22,901
  • 6
  • 64
  • 72
  • 1
    If you open the jar file, and open the manifest inside, it will have a `Class-Path` entry. That's how it will find the class when you run it from the command line. When done programmatically from a classloder, I don't think it uses that manifest though. – sstan Jun 22 '15 at 15:08
  • @sstan Nope, the `MANIFEST.MF` file does not contain such entry. I updated the question with its contents. – serejja Jun 22 '15 at 15:12
  • Looks like the javax mailing jar is not in your class path. How exactly are you running the program that is not working (e.g. from cmd line with java -cp some.jar com.mypackage.MyClass). More info here: http://stackoverflow.com/questions/18413014/run-jar-from-command-line-and-specify-classpath – John Jun 22 '15 at 15:14
  • @serejja: I'm, with you. No idea how you're getting `java -jar my-jar-file.jar` to work in the first place. – sstan Jun 22 '15 at 15:18
  • @John I run it with `java -cp my-another-jar.jar com.foo.bar.AnotherMain`. That AnotherMain contains code that I described above. I understand that it seems I have javax mailing missing from classpath, but the thing that really bothers me is why does it work when I just `java -jar` the jar file I'm trying to invoke from my code? – serejja Jun 22 '15 at 15:18
  • Try: java -cp "my-another-jar.jar;lib/*;javax.mail.jar" com.foo.bar.AnotherMain. More info here: http://stackoverflow.com/questions/219585/setting-multiple-jars-in-java-classpath (an have the javax.mail.jar in the directory you are running from). – John Jun 22 '15 at 15:24

1 Answers1

0

Ok, I got this solved by adding Thread.currentThread().setContextClassLoader(loader);:

URLClassLoader loader = URLClassLoader.newInstance(new URL[]{new File("my-jar-file.jar").toURI().toURL()});
Thread.currentThread().setContextClassLoader(loader);
Class<?> serverClass = loader.loadClass("com.foo.bar.application.MyMain");

Method main = serverClass.getMethod("main", String[].class);
String[] params = new String[]{"-flag", "value"};
main.invoke(null, (Object) params);

Now everything works as expected and I don't get ClassNotFoundException anymore, but I still don't quite understand why this works. It seems to me that it was trying to load additional classes/resources with a different ClassLoader and thus was failing to do that, and by calling setContextClassLoader I'm explicitly saying to use the provided ClassLoader.

serejja
  • 22,901
  • 6
  • 64
  • 72