I'm using Gradle to build a project. This is my build.gradle:
plugins { id 'application' }
group 'my.group'
version '1.0'
sourceCompatibility = 1.11
repositories { mavenCentral() }
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
implementation group: 'org.xerial', name: 'sqlite-jdbc', version: '3.7.2'
}
mainClassName = 'my.group.App'
jar {
manifest {
attributes(
'Main-Class': mainClassName,
'Class-Path': configurations.runtimeClasspath.files.collect { it.name }.join(' ')
)
}
from configurations.runtimeClasspath
into ''
}
This correctly generates a jar file with the following META-INF/MANIFEST.MF:
Manifest-Version: 1.0
Main-Class: my.group.App
Class-Path: sqlite-jdbc-3.7.2.jar
[newline]
And in the root of this jar file, there is indeed the sqlite-jdbc-3.7.2.jar file referenced in the manifest; I manually verified that inside this jar file there is a class called org.sqlite.JDBC.
However, running the generated jar with java -jar jarfile.jar results in:
Exception in thread "main" java.lang.ExceptionInInitializerError
at my.group.LEManagerSqlite.<init>(LEManagerSqlite.java:64)
at my.group.LEManager.createLEManager(LEManager.java:80)
at my.group.GuiFrame.<init>(GuiFrame.java:60)
at my.group.App.openFromFile(App.java:23)
at my.group.App.main(App.java:19)
Caused by: java.lang.RuntimeException: java.lang.ClassNotFoundException: org.sqlite.JDBC
at my.group.SqliteConnectionManager.<clinit>(SqliteConnectionManager.java:17)
... 5 more
Caused by: java.lang.ClassNotFoundException: org.sqlite.JDBC
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
at java.base/java.lang.Class.forName0(Native Method)
at java.base/java.lang.Class.forName(Class.java:315)
at my.group.SqliteConnectionManager.<clinit>(SqliteConnectionManager.java:14)
... 5 more
For reference, SqliteConnectionManager has a static initializer which loads org.sqlite.JDBC, which is what is referenced as SqliteConnectionManager.java:14 in the stack trace:
Class.forName("org.sqlite.JDBC");
I tested this with OpenJDK 11 and OpenJ9 11, with identical results. I conclude that I'm doing something wrong, but I cannot understand what.
I found out what I was doing wrong: the documentation clearly says (doh!) that the Class-Path directive looks for additional classpath resources in the local filesystem or the network.
Loading additional classes from jar files included in the main jar file is not supported by the JVM, and thus requires custom loading code. A simpler alternative is to unpack the additional classes from the jar files and include them directly. A jar file built this way is referred to as a "fat jar" (or "uber jar").
There is a Gradle plugin called Shadow which can build such jars without any further configuration.
Related
I'm trying to use log4j in my java core console application. In the documentation there is sample code on how to add Gradle dependency for log4j:
https://logging.apache.org/log4j/2.x/maven-artifacts.html
dependencies {
compile group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.14.1'
compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.14.1'
}
I pasted it to my build.gradle file and wrote sample code for running the code.
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class Program {
private static final Logger logger = LogManager.getLogger("HelloWorld");
public static void main(String[] args) {
logger.error("Just a test error entry");
}
}
along with that I also added manifest in order to run the code from the console.
jar {
manifest {
attributes "Main-Class": "Program"
}
}
If I build it with Gradle - everything is built successfully. But if I try to run java -jar from my build directory then I got
Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/logging/log4j/LogManager
at Program.<clinit>(Program.java:5)
Caused by: java.lang.ClassNotFoundException: org.apache.logging.log4j.LogManager
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:636)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:182)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:519)
... 1 more
I found 1 workaround: modify to jar{} section:
jar {
manifest {
attributes "Main-Class": "Program"
}
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
}
and it started working, I even try to zip .jar file and saw that there is log4j dependency added.
But what is I don't want transitive dependency on log4j? If I set implementation group instead of compile group it doesn't work again. Is there any complete example of using log4j?
On top of that, one thing to point, while it is not working with java -jar <.jar>, for some reason it still works in Intellij idea. Could someone explain me why?
The problem you are encountering is caused by a problem in the application's classpath, when you launch the application (cf. What is a classpath and how do I set it?).
Gradle has many options to help you with setting the classpath right. Since you don't mention any Gradle version, I'll use version 7.1 in the examples. Assume you have this build.gradle file in a project named appName:
plugins {
id 'application'
}
dependencies {
implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.14.1'
runtimeOnly group: 'org.apache.logging.log4j', name: 'log4j-core', version: '2.14.1'
}
application {
mainClass = 'com.example.App'
}
(log4j-core is not necessary to compile your code, so it is in the runtimeOnly dependency configuration, cf. dependency configurations)
Manually setting the classpath
If you generate a plain JAR file (execute the jar task and look in build/libs) you can execute the application with:
java -cp log4j-api-2.14.1.jar:log4j-core-2.14.1.jar:appName.jar com.example.App
(I assume all jars are in the current directory; the path separator is system specific, on Windows it is ;)
This becomes easily difficult to get right, therefore the application Gradle plugin generates a shell and a batch script to help you: execute the installDist task and look into build/install/appName (or you can use the distZip or distTar tasks for a compressed version of this folder). Setting up the correct command is reduced to calling:
bin/appName
Hardcoding the classpath in the Manifest
You can also hardcode the dependencies needed in the JARs manifest file. In Gradle you can do it with:
jar {
manifest {
attributes(
'Main-Class': 'com.example.App',
'Class-Path': configurations.runtimeClasspath.collect { it.name }.join(' ')
)
}
}
to obtain a manifest file with all the information needed to start the application:
Main-Class: pl.copernik.gradle.App
Class-Path: log4j-core-2.14.1.jar log4j-api-2.14.1.jar
Notice the usage of runtimeClasspath, which is a configuration that contains both the runtimeOnly and implementation dependency configurations needed to start the application.
If the dependency jars are in the same folder as appName.jar, you can run the application with:
java -jar appName.jar
Spring Boot loader
If you want to have all dependencies in a single JAR, you can use the Spring Boot Gradle plugin instead of the application plugin:
plugins {
id 'java'
id 'org.springframework.boot' version '2.5.3'
}
The bootJar task will produce an appName.jar resembling in its structure the appName.zip file produced by the distZip file. However the shell/batch scripts are replaced with Java code, so you can call:
java -jar appName.jar
Fat Jar
What you did in your question with:
jar {
manifest {
attributes "Main-Class": "com.example.App"
}
from {
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
}
}
is usually called fat JAR. Basically it is produced by unzipping your jar file and all dependencies into a single folder and zipping it back together.
This works, but has some disadvantages: e.g. you might lose licence information on the dependencies (which might be a licence violation) and you will not be able to replace the dependencies with new versions without recompiling. See this question for an example of an absurdly large fat jar.
I am developing a Minecraft plugin with Spigot, so far no problem.
I have added several dependencies so an dependencies that refers to a . jar pixearth-core-1.0 located at the root of the project in a folder libs. When I develop, I have access to the classes in the pixearth-core-1.0 library.
When I compile the project there is no problem, however when I run the plugin on the server, I have the following error:
[01:58:17 ERROR]: Error occurred while enabling Idle v1.0-SNAPSHOT (Is it up to date?)
java.lang.NoClassDefFoundError: pixearth/idleplugin/database/DatabaseManager
at pixearth.idle.Main.onEnable(Main.java:26) ~[?:?]
at org.bukkit.plugin.java.JavaPlugin.setEnabled(JavaPlugin.java:264) ~[spigot.jar:git-Spigot-fe3ab0d-162bda9]
at org.bukkit.plugin.java.JavaPluginLoader.enablePlugin(JavaPluginLoader.java:339) [spigot.jar:git-Spigot-fe3ab0d-162bda9]
at org.bukkit.plugin.SimplePluginManager.enablePlugin(SimplePluginManager.java:403) [spigot.jar:git-Spigot-fe3ab0d-162bda9]
at org.bukkit.craftbukkit.v1_13_R1.CraftServer.enablePlugin(CraftServer.java:426) [spigot.jar:git-Spigot-fe3ab0d-162bda9]
at org.bukkit.craftbukkit.v1_13_R1.CraftServer.enablePlugins(CraftServer.java:340) [spigot.jar:git-Spigot-fe3ab0d-162bda9]
at net.minecraft.server.v1_13_R1.MinecraftServer.m(MinecraftServer.java:562) [spigot.jar:git-Spigot-fe3ab0d-162bda9]
at net.minecraft.server.v1_13R1.MinecraftServer.g(MinecraftServer.java:524) [spigot.jar:git-Spigot-fe3ab0d-162bda9]
at net.minecraft.server.v1_13_R1.MinecraftServer.a(MinecraftServer.java:423) [spigot.jar:git-Spigot-fe3ab0d-162bda9]
at net.minecraft.server.v1_13_R1.DedicatedServer.init(DedicatedServer.java:288) [spigot.jar:git-Spigot-fe3ab0d-162bda9]
at net.minecraft.server.v1_13_R1.MinecraftServer.run(MinecraftServer.java:686) [spigot.jar:git-Spigot-fe3ab0d-162bda9]
at java.lang.Thread.run(Thread.java:748) [?:1.8.0_221]
Caused by: java.lang.ClassNotFoundException: pixearth.idleplugin.database.DatabaseManager
The class pixearth/idleplugin/database/Databasemanager is located in the pixearth-core-1.0 library.
Only the pixearth-core-1.0 library is not exported during compilation but spigot-api and SQLiteQueryBuilder are exported.
I’m not sure why, do you have any idea how to fix the problem?
My build.gradle :
apply plugin: 'java'
group = pluginGroup
version = pluginVersion
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
mavenCentral()
maven {
name = 'spigotmc-repo'
url = 'https://hub.spigotmc.org/nexus/content/groups/public/'
}
maven {
name = 'sonatype'
url = 'https://oss.sonatype.org/content/groups/public/'
}
maven {
url "https://jitpack.io/"
}
flatDir {
dirs 'libs'
}
}
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
compile 'org.spigotmc:spigot-api:1.13-R0.1-SNAPSHOT'
compile 'com.github.alexfu:SQLiteQueryBuilder:0.1.1'
compile files('libs\pixearth-core-1.0.jar')
}
import org.apache.tools.ant.filters.ReplaceTokens
processResources {
from(sourceSets.main.resources.srcDirs) {
filter ReplaceTokens, tokens: [version: version]
}
}
Your plugin is calling on the library during runtime but it is not found. It needs to either be loaded by spigot as a plugin on its own, or included in your jar. There is a Gradle plugin to help you with this called Shadow. You can find it on GitHub. Here is the user guide but to summarize, add that to your plugins in buildscript and use task shadowJar. Anything with runtime classpath will be included inside your final jar.
It is also recommended to use implementation instead of compile. It still works in some Gradle versions for compatibility issues but it is deprecated and has been removed from Gradle 7. More information on Gradle docs site
I have set up an OpenJDK 12 project in IntelliJ (2019.2) using the built-in Gradle support. To design the GUI I'm using JavaFX 12. I have followed and read the setup guide several times, I have no issues running the program in my IDE, the issue is when I try to build a .jar file for distribution that I run into problems. I have not been able to find a solution that works so far and I've searched QUITE a lot, nearly tearing my hair out over this. Currently when i try to run my jar file with java -jar "jarName".jar I get the following error:
Exception in thread "main" java.lang.NoClassDefFoundError: javafx/application/Application
at java.base/java.lang.ClassLoader.defineClass1(Native Method)
at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1016)
at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:151)
at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:802)
at java.base/jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(BuiltinClassLoader.java:700)
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:623)
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
at com.CAM.Starter.main(Starter.java:6)
Caused by: java.lang.ClassNotFoundException: javafx.application.Application
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:583)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
... 10 more
I have tried moving my main class to a separate one that doesn't extend Application, which is what gives the above error. Without moving my Main class I get a different error.
My build.gradle looks like this currently:
plugins {
id 'java'
id 'application'
id 'org.openjfx.javafxplugin' version '0.0.8'
}
group 'ClassicAddonManager'
version '0.2'
sourceCompatibility = 11
repositories {
mavenCentral()
}
javafx {
version = "12.0.2"
modules = [ 'javafx.controls', 'javafx.fxml' ]
}
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.12'
compile 'net.sourceforge.htmlunit:htmlunit:2.13'
compile group: 'com.google.code.gson', name: 'gson', version: '2.7'
compile group: 'net.lingala.zip4j', name: 'zip4j', version: '1.2.4'
}
jar {
manifest {
attributes 'Main-Class': 'com.CAM.Starter'
}
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
}
If I have my main method extending Application then I get an error stating that my main class could not be found even though I can see it is present in the generated .jar file.
All I'm trying to do, is to generate a file that a friend with no knowledge of programming could run. Ideally, a file that they could run without having to install Java first. I know it should be possible to do this, but Gradle is new to me so I'm not sure if that is what is causing all this headache. Is there potentially an easier way to deploy? Especially given that this is a solo-project?
EDIT
I have tried the modular part of the guide. Doing that I have over 100 error when attempting to build. They are all something in the vein of:
javafx.graphicsEmpty reads package org.xml.sax from both xml.apis and java.xml
If you want to do a fat jar using Gradle but not a shadow plugin, usually you will do:
jar {
manifest {
attributes 'Main-Class': 'com.CAM.Starter'
}
from {
configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
}
}
However, there is an important fact: compile is deprecated, and the JavaFX plugin uses implementation by default.
As a consequence, configuration.compile might be empty, or at least, it won't contain the JavaFX classes.
The solution is to check the runtimeClasspath configuration, as we will have all the classes there, and we can make the fat jar:
jar {
manifest {
attributes 'Main-Class': 'com.CAM.Starter'
}
from {
configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) }
}
}
This is explained in the OpenJFX docs, https://openjfx.io/openjfx-docs/#modular, section non-modular projects, subsection gradle.
Once you have your fat jar, you can run it with:
java -jar yourFatJar.jar
Note that double-clicking on it won't work, as explained in detail here, so it can be convenient to create a small batch instead.
A better solution is to do a modular project and use jlink to create a runtime image (it also includes a launcher script). You can distribute this image to other users that don't have even JDK installed. With gradle, you can just include the 'org.beryx.jlink' plugin, like in this sample.
And you can also use the early version of jpackage to create and distribute an installer.
I've problem with JXBrowser license file. I've followed https://jxbrowser-support.teamdev.com/docs/quickstart/gradle-config.html official guide to use with Gradle and this is my current build.Gradle
Build seem normal and no any error.
plugins {
id 'java'
}
sourceSets {
main {
java {
srcDir 'src'
}
}
test {
java {
srcDir 'test'
}
}
}
jar {
manifest {
attributes 'Main-Class': 'main.Main'
}
}
version '1.0'
sourceCompatibility = 1.8
repositories {
mavenCentral()
maven { url = 'http://maven.teamdev.com/repository/products' }
}
ext {
jxBrowserVersion = '6.22'
}
dependencies {
compile group: 'org.postgresql', name: 'postgresql', version: '42.2.5'
compile "com.teamdev.jxbrowser:jxbrowser-cross-platform:${jxBrowserVersion}"
compile 'com.maxmind.geoip2:geoip2:2.12.0'
compile files("$rootDir/license.jar")
testCompile group: 'junit', name: 'junit', version: '4.12'
}
Error happens when it run I'm not sure my build.Gradle is wrong or something here is error
Exception in thread "main" java.lang.NoClassDefFoundError: com/teamdev/jxbrowser/chromium/PermissionHandler
at main.Main.main(Main.java:8)
Caused by: java.lang.ClassNotFoundException: com.teamdev.jxbrowser.chromium.PermissionHandler
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:582)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
... 1 more
I suppose you see this exception when you run your Java application through an executable JAR file.
This exception indicates that you didn't include JxBrowser JAR files into your application class path. I see that you include JxBrowser JAR files as compile dependencies into your build.gradle. It's OK during build, but not for production.
Please make sure that you add JxBrowser JAR files into the classpath of your application. For example, you can download the required JAR files, put them into some directory, and configure the Class-Path attribute in your JAR file as show in the Oracle's tutorial.
Or you can use the Gradle plugin that includes all dependencies into a single fat JAR. In this case you don't need to configure Java app classpath.
Now it's work using JDK & JRE < 10
I have created a simple Java program in IntelliJ ide. I used few libraries and when I try to export the jar as an artifact and run it from command line I get the following error:
Exception in thread "main" java.lang.NoClassDefFoundError: com/google/common/util/concurrent/FutureCallback
at java.lang.Class.getDeclaredMethods0(Native Method)
at java.lang.Class.privateGetDeclaredMethods(Class.java:2615)
at java.lang.Class.getMethod0(Class.java:2856)
at java.lang.Class.getMethod(Class.java:1668)
at sun.launcher.LauncherHelper.getMainMethod(LauncherHelper.java:494)
at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:486)
Caused by: java.lang.ClassNotFoundException: com.google.common.util.concurrent.FutureCallback
at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
at java.lang.ClassLoader.loadClass(ClassLoader.java:425)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
at java.lang.ClassLoader.loadClass(ClassLoader.java:358)
... 6 more
Compiling & Running the program inside IntelliJ works great.
My project is configured as a gradle project with the following build.gradle:
group 'marianpavel.com'
version '1.0'
apply plugin: 'java'
sourceCompatibility = 1.5
repositories {
mavenCentral()
maven {
url "http://repo.bastian-oppermann.de"
}
}
dependencies {
testCompile group: 'junit', name: 'junit', version: '4.11'
compile 'de.btobastian.javacord:javacord:2.0.11'
compile 'ch.qos.logback:logback-classic:1.0.13'
compile 'de.btobastian.sdcf4j:sdcf4j-core:1.0.2'
compile 'de.btobastian.sdcf4j:sdcf4j-javacord:1.0.2'
compile 'org.jsoup:jsoup:1.9.2'
}
I have exported the jar as followed: Project Structure -> Artifacts -> Jar -> From Module with dependencies -> Added the main source folder and all the libraries from library files, added a manifest and a main class and exported the jar.
I am trying to figure this out for days and I don't understand why it cant find the class.
I think there is a misconception on your end.
Your project consists of two important parts:
Your own code
External libraries that your code is using
When you run your project within an IDE, and your project setup is correct, then both things are in your classpath.
But when you create a JAR for your project, it very much depends on your setup. Meaning: the "default" would be that a JAR created for your project only contains the "1." parts. Because you do not want that all external classes are added to "your" JAR in the first place.
So, to resolve that: when you want to run your program from your JAR on the command line, then you will need all those external JARs to be in your classpath as well! Because, as said: to run, you need both "1" and "2" parts to be present in the classpath.
Conclusion: you should check what gradle puts in the JAR it creates for you. I am pretty sure: that JAR only contains "1".
(and for the record, it is fine like that. it is possible to bundle everything into a single jar, but that will require work on your end)