I have a jMonkeyEngine 3 (jME3) Gradle project that works fine when running it within the Eclipse IDE, but when I try to run the generated JAR file from the command line, it throws a “java.lang.NoClassDefFoundError: com/simsilica/es/EntityData” exception.
The Gradle build file looks like this:
plugins {
// Apply the java-library plugin for API and implementation separation.
id 'java-library'
id 'eclipse'
id 'application' // Add the application plugin for creating the runnable JAR
}
version '1.0-SNAPSHOT'
java {
sourceCompatibility = '11'
targetCompatibility = '11'
}
repositories {
// Use Maven Central for resolving dependencies.
//mavenCentral()
flatDir {
dirs 'D:/Program_Files/jME3.5.2-stable/lib'
}
}
dependencies {
// jME3 Core
implementation files('D:/Program_Files/jME3.5.2-stable/lib/jme3-core.jar')
// jME3 Desktop
implementation files('D:/Program_Files/jME3.5.2-stable/lib/jme3-desktop.jar')
// jME3 Plugins
implementation files('D:/Program_Files/jME3.5.2-stable/lib/jme3-plugins.jar')
// jME3 LWJGL3
implementation files('D:/Program_Files/jME3.5.2-stable/lib/jme-lwjgl3/jme3-lwjgl3-3.5.2-stable.jar')
// LWJGL3
implementation files('D:/Program_Files/jME3.5.2-stable/lib/lwjgl3.3.2/lwjgl.jar')
implementation files('D:/Program_Files/jME3.5.2-stable/lib/lwjgl3.3.2/lwjgl-natives-windows.jar')
implementation files('D:/Program_Files/jME3.5.2-stable/lib/lwjgl3.3.2/lwjgl-opengl.jar')
implementation files('D:/Program_Files/jME3.5.2-stable/lib/lwjgl3.3.2/lwjgl-opengl-natives-windows.jar')
implementation files('D:/Program_Files/jME3.5.2-stable/lib/lwjgl3.3.2/lwjgl-openal.jar')
implementation files('D:/Program_Files/jME3.5.2-stable/lib/lwjgl3.3.2/lwjgl-openal-natives-windows.jar')
implementation files('D:/Program_Files/jME3.5.2-stable/lib/lwjgl3.3.2/lwjgl-glfw.jar')
implementation files('D:/Program_Files/jME3.5.2-stable/lib/lwjgl3.3.2/lwjgl-glfw-natives-windows.jar')
// jME Tools
implementation files('D:/Program_Files/jME3.5.2-stable/lib/jme-tools/zay-es-1.3.2.jar')
implementation files('D:/Program_Files/jME3.5.2-stable/lib/jme-tools/slf4j-api-1.7.5.jar')
// Gson
implementation files('D:/Program_Files/jME3.5.2-stable/lib/gson.jar')
}
jar {
manifest {
attributes(
'Main-Class': 'runnergame.Main'
)
}
// Include the JRE in the JAR
from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
// Exclude unnecessary files
exclude 'META-INF/*.RSA', 'META-INF/*.SF', 'META-INF/*.DSA'
}
application {
// Specify the main class for the application
mainClassName = 'runnergame.Main'
}
Why is the JAR file not working properly, even though the project runs fine in the Eclipse IDE?
This is very odd (although i think not the cause of your problem). Why are you doing this rather than using normal gradle dependencies (which would download the jars and your dependencies dependencies automatically)
Are you creating a regular jar, or a fat jar, or a full distribution?
A normal jar will just have your application code in it, no dependencies (and will fail in the way you describe).
A fat jar will include all your dependencies in a single jar file (and wont have these problems).
A full distribution will include all the jars seperately, a bat or sh file to kick the whole thing off (or an exe if youre feeling fancy) and possibly a bundled JRE
I appreciate you taking the time to analyze the situation and provide helpful suggestions.
To address your points:
Regarding the implementation files(‘D:/Program_Files/jME3.5.2-stable/lib/lwjgl3.3.2/lwjgl.jar’) line: You’re correct, this is an unusual approach. I have the library stored locally and my internet connection is slow and unreliable Regarding the type of build: Based on your explanation, I believe a full distribution version is the best approach for my use case. Since my internet connection is slow, I want to have a standalone package that includes all the necessary JARs and scripts to run the application, without relying on downloading dependencies at runtime.
This will ensure my application can be deployed and run without any internet connectivity issues.
That makes sense. I think gradle will cache things but if you want to bring your dependencies over on a hard drive maybe a file based dependency is all you can do.
I think the initialiser example should still work for building full distributions even if you are defining your dependencies as file locations
From the command line, run your app with “gradle run”
If you want to run from the jar then you will have to include a bunch of stuff on the command line… which the application plugin has already nicely done for you in a batch file when you build a fill distribution:
“gradle distZip”
…then unzip the zip and run the bat/shell script in there.
And [fat jars] was the single most ridiculous way to bundle an application.
Why do you say that? I think it’s the default way spring boot apps get built and deployed (and Spring seem to know what they are doing). It can be pretty convenient to have a single file that doesnt need to be exploded out of a zip to run.
Obviously only really useful in environments where the JRE is provided for you but (outside game development) you often can assume that. I wouldnt suggest it if you’re also bundling an JRE though (as it’s a bit pointless at that point)
Because the bundling job is very different from the running job, essentially running your app using ./gradlew :app:run will compile the Java files into byte code (.class) into your build/classess and will run the main class, while running a jar requires the previous step of compiling plus packaging the Java executable archive with other dependencies in the same file or will just have to write the class paths to the external dependencies in your META-INF file… Depending on your assembling Jar configuration, I am not sure about the functionality of this from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }, but I am anticipating that it adds the compiled .class files from the previous step into the Jar, it’s not a very good way to handle dependencies from my point of view, but anyway you have to build first before assembling and perhaps also run your application, and then you can check the output by extracting the jar itself and looking inside the META-INF and the class paths.
I prefer packaging the dependencies as they are in a separate directory entry just as the SDK does, and then including them in the class path of the application, here is how:
You can use something like these tasks to automate copying the dependencies from the runtime and/or compiletime class paths or even from your .gradle/caches directly, and including their paths in the META-INF (which you can type manually by the way).
The problems with the fat Jars appear when people/companies scale-up their games or applications, fat Jars hinder the user from updating or installing new dependencies (including Games DLCs which might have larger asset files), and so the user is forced to delete the whole executable and re-download again which incurs additional time and data that will be of course significant in larger games.
This is generally true, however (if I recall correctly) if you use something like Steam for distributing your game they will have this mostly handled for you. Even if you upload a new version as one big jar, steam will check for differences and the upload/download time will be much quicker if there aren’t too many changes between the old and new versions, and will usually never take as long as the initial install.