Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Building FatJar for JavaFX with gradle and intellij, getting NoClassDefFOundError

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
like image 407
erik p Avatar asked Aug 11 '19 18:08

erik p


1 Answers

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.

like image 138
José Pereda Avatar answered Sep 23 '22 12:09

José Pereda