NoClassDefFoundException when using LWJGL in a Maven subproject (Eclipse)

Started by freduni, May 03, 2020, 13:48:05

Previous topic - Next topic

freduni

I am familiar with LWJGL development. It's been about ten years I am using LWJGL and it is a great product.

I have a scenario where I get a NoClassDefFoundException and can't understand how to solve it "cleanly".

In Eclipse Java, I have the following situation:

Project1
+- Project2
   +- Project3


Project1, Project2 and Project3 are regular Java projects that have been created through File > New Java project, then converted to Maven projects through a right-click in Project Explorer > Maven > Convert to Maven. Project1 contains my application entrypoint (static main method). Project1 code calls Project2 code, which in turns calls Project3 code:

// Project1
public class Project1Class {
    public static void main(String[] args)
    {
        Project2Class.foo();
        System.out.println("Done");
    }
}
// Project2
public class Project2Class {
    public static void foo()
    {
        Project3Class.bar();
    }
}
// Project3
public class Project3Class {
    public static void bar()
    {
       // LWJGL sample code (any will do, such as this one: https://github.com/JOML-CI/joml-lwjgl3-demos/blob/master/src/org/joml/lwjgl/LwjglDemo.java)
    }
}


Project3 uses LWJGL Maven dependencies.

My problem is the following:

When running or debugging Project1, I can see that Project1, Project2 and Project3 'target/classes' folders are in the classpath, as expected, Maven dependencies for Project1 and Project2 also are in the classpath, but the classpath entries for the Maven dependencies of Project3 are missing. As a result, LWJGL's native libraries cannot be loaded, and I get an exception:

Exception in thread "main" java.lang.NoClassDefFoundError: org/lwjgl/Version
	at fc.PROJ3.HelloWorld.run(HelloWorld.java:50)
	at fc.PROJ3.HelloWorld.main(HelloWorld.java:142)
	at fc.PROJ2.PROJ2Class.foo(PROJ2Class.java:10)
	at fc.PROJ1.PROJ1Application.main(PROJ1Application.java:8)
Caused by: java.lang.ClassNotFoundException: org.lwjgl.Version
	at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	... 4 more


Now, if I move the LWJGL dependency declarations from Project3 to Project2, then they do end up in the classpath, and calling LWJGL code succeeds (the native library can be loaded).

I tried with Eclipse 2019-09 and 2020-03, same result.

It seems Eclipse does not add Maven dependencies that reside in indirect dependencies. How can I tell it to add Maven dependencies in the classpath for all dependencies, i.e. both direct and indirect?

I really don't want to declare the LWJGL dependencies in the POM of Project2, because it's got nothing to do there.

KaiHH

I cannot reproduce this. Can you show the pom.xml files of project 1 through 3, especially the dependencies section?
I've created the same three project setup as you described (though I cannot upload the 9KB zip file because I am getting HTTP 500 errors), so here's the gist with the pom.xml files:
https://gist.github.com/httpdigest/f18c54e7c525a6e72ad32a39513ce128
Does your setup look similar and if not, can you build a MCVE?

freduni

Quote from: KaiHH on May 03, 2020, 14:20:27
I cannot reproduce this. Can you show the pom.xml files of project 1 through 3, especially the dependencies section?
I've created the same three project setup as you described (though I cannot upload the 9KB zip file because I am getting HTTP 500 errors), so here's the gist with the pom.xml files:
https://gist.github.com/httpdigest/f18c54e7c525a6e72ad32a39513ce128
Does your setup look similar and if not, can you build a MCVE?
I created a link: https://preview.tinyurl.com/yce4u9rw
The files will expire tomorrow.

When I run or debug Project1, I just click the Run or Debug button, that's all. Projects are built automatically as part of the automatic-build of Eclipse.

freduni

Quote from: KaiHH on May 03, 2020, 14:20:27
I cannot reproduce this. Can you show the pom.xml files of project 1 through 3, especially the dependencies section?
I've created the same three project setup as you described (though I cannot upload the 9KB zip file because I am getting HTTP 500 errors), so here's the gist with the pom.xml files:
https://gist.github.com/httpdigest/f18c54e7c525a6e72ad32a39513ce128
Does your setup look similar and if not, can you build a MCVE?
Ok, I found the difference between your files and mine.

Your Project2 pom has this declaration (mine doesn't):

<dependency>
			<groupId>fc.PROJ3</groupId>
			<artifactId>fc.PROJ3</artifactId>
			<version>0.0.1-SNAPSHOT</version>
		</dependency>


But there IS something odd with Eclipse. If I move LWJGL code to Project2, it won't be necessary for me to add:

<dependency>
			<groupId>fc.PROJ2</groupId>
			<artifactId>fc.PROJ2</artifactId>
			<version>0.0.1-SNAPSHOT</version>
		</dependency>

in Project1's pom.xml file.

Why does Eclipse put Project2's Maven dependencies in the classpath, but doesn't put Project3's Maven dependencies in the classpath?

I mean, Eclipse seems to resolve things correctly for subprojects (direct dependencies), but not for sub-subprojects (transitive dependencies)? Why? That doesn't make sense.

Is there a way I can have Eclipse automatically declare Maven dependencies when I add project dependencies through the user interface?


KaiHH

> Your Project2 pom has this declaration (mine doesn't):

So basically you are saying that no project (neither project 1 nor project 2) has a dependency to project 3 declared in their pom.xml files? Then it obviously will not work.
There is no single Eclipse class path, but every project has its own class path. If project 1 declares a dependency on project 2, then project1's classpath will contain all classpath entries of project2 (including project2's direct pom.xml dependencies).
But if no one (neither project1 nor project2) has a pom.xml dependency on project3, then neither project1's classpath nor project2's classpath will contain project3.
Eclipse project dependencies and Maven pom.xml dependencies (on workspace-resolved projects) are two different things.
However, when you declare a dependency on a workspace-resolved project inside of the pom.xml, then Eclipse will also make it a project dependency. But NOT the other way around, as Eclipse will not automatically modify the pom.xml for you.

freduni

Quote from: KaiHH on May 03, 2020, 15:52:32
Eclipse project dependencies and Maven pom.xml dependencies (on workspace-resolved projects) are two different things.
However, when you declare a dependency on a workspace-resolved project inside of the pom.xml, then Eclipse will also make it a project dependency. But NOT the other way around, as Eclipse will not automatically modify the pom.xml for you.
Indeed, I tried your suggestion and it worked.

If I understand correctly, you are saying that

- when evolving with Maven projects within Eclipse, I should not use the Java Build Path 'Projects' tab, and 'Add' button. Basically, I should not use Project dependencies ('.project'-file based).
- I should use explicit dependencies in the pom.xml file. Eclipse will actually recognize these dependencies and make sure things are built correctly and will consolidate the classpath and so on.

Would you know how I can add a Maven dependency *between projects* using the Eclipse UI? Can I only do it by manually editing pom.xml?

freduni

I got it. It is in the dependencies tab at the bottom of the pom.xml file.

This is just incredible that things have been working without issues for me so far. Packaging and deployment has also been working fine since I was using the built-in JAR mechanism of Eclipse (ie. generate Runnable JAR), not Maven-based packaging.

Thank you heaps for your help, this great enhances my understanding of Eclipse and Maven.

KaiHH

Sure, glad I could help and that you figured it out! :)
Just one more background information about Eclipse's build path model:
The actual reason why project 1 did not know about the LWJGL dependencies in project 3 was because, as you've found, project 2 only had a Eclipse-internal build path project dependency on project 3. So project 2 used the Eclipse-internal project dependency mechanism (added via the "Projects" tab of project 2's build path settings).
Now there are project dependencies and library dependencies (as seen in the "Libraries" tab of a project's build path settings). Both contribute to the final classpath of a project. However, when you use Eclipse's "Project" dependency, then that is not managed by Maven (actually via the m2e plugin in Eclipse) and you can actually use Eclipse's "Order and Export" settings (also found as a tab in the project's build path settings). Eclipse's project dependencies by default are not transitively inherited by other depending projects, but this can be enabled via the "Order and Export" settings by simply checking/marking the project dependency. So you could've checked project 3 in project 2's "Order and Export" tab and that would have allowed project 1 to actually see project 3's dependencies.
But anyways, when you use Maven you should definitely not tamper in any way manually with Eclipse's (or any IDE's) build path settings. Let Maven handle that by itself using Eclipse's "Maven workspace resolution" (which you can also enable/disable for every project in the project's settings under "Maven").

freduni

I agree, Maven-based project dependencies are the safest way to go. This way, I can create the final JAR with Maven and not the 'Runnable JAR' export tool of Eclipse, which I cannot automate anyway.

Quote from: KaiHH on May 03, 2020, 18:11:14
[...]
Eclipse's project dependencies by default are not transitively inherited by other depending projects, but this can be enabled via the "Order and Export" settings by simply checking/marking the project dependency. So you could've checked project 3 in project 2's "Order and Export" tab and that would have allowed project 1 to actually see project 3's dependencies.
For the sake of completeness, I tried this, but I could not get it to work.

I got rid of the Maven dependencies (between the 3 projects), and defined two Eclipse project dependencies, Project1 -> Project2, and Project2 -> Project3.

In Project3, I go to Java Build Path > Order and Export and click the checkbox 'Maven Dependencies'
In Project2, I go to Java Build Path > Order and Export and click the checkbox 'Maven Dependencies' and Project3
In Project1, I go to Java Build Path > Order and Export and click the checkbox 'Maven Dependencies' and Project2

Project3's Maven dependencies (LWJGL) do not get to the classpath.

EDIT: to further clarify, 'Order and Export' between projects seems to make Project3's built-in or JAR-based classes accessible to Project1, at compile time. For instance, a 'Project3Class' in 'Project3' will be visible to Project1 code if I chain 'Order and Export' dependencies. Maven dependency classes, for instance LWJGL classes, will also be available at compile time to Project1 code. However, at runtime, LWJGL will not be in the classpath.