1

I have a problem with the distribution of my JavaFX application using gradle:installDist. I reduced the problem to a basic example.

I have the following setup for my application:

  • OpenJDK 11
  • OpenJFX 11
  • Gradle 5.0
  • Hibernate 5.4
  • h2database 1.4.197

build.gradle:

plugins {
    id 'java'
    id 'application'
    id 'org.openjfx.javafxplugin' version '0.0.5'
}

group 'de.testing'
version '1.0-SNAPSHOT'



repositories {
    mavenCentral()
}

dependencies {
    compile group: 'org.hibernate', name: 'hibernate-core', version: '5.4.0.Final'
    compile group: 'com.h2database', name: 'h2', version: '1.4.197'
}

mainClassName = 'de.testing.Main'

Code:

package de.testing;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public class Main {
    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("thePersistenceUnit");
        EntityManager em = emf.createEntityManager();
    }
}

persistence.xml:

<?xml version="1.0" encoding="UTF-8" ?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
 http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">

    <persistence-unit name="thePersistenceUnit" transaction-type="RESOURCE_LOCAL">
        <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
        <properties>
            <property name="connection.driver_class" value="org.h2.Driver"/>
            <property name="hibernate.connection.url" value="jdbc:h2:~/testing"/>
            <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
            <property name="hibernate.hbm2ddl.auto" value="update"/>
            <property name="hibernate.show_sql" value="true" />
        </properties>
    </persistence-unit>
</persistence>

When I run this inside my IDE, everything is fine. However when I use gradle:installDist to distribute the application und try to run the startscript, I get the following exception:

.\testing.bat
Jan. 04, 2019 6:53:22 NACHM. org.hibernate.jpa.internal.util.LogHelper logPersistenceUnitInformation
INFO: HHH000204: Processing PersistenceUnitInfo [
        name: thePersistenceUnit
        ...]
Jan. 04, 2019 6:53:22 NACHM. org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {[WORKING]}
Jan. 04, 2019 6:53:22 NACHM. org.hibernate.cfg.Environment <clinit>
INFO: HHH000206: hibernate.properties not found
Exception in thread "main" java.lang.NoClassDefFoundError: net/bytebuddy/NamingStrategy$SuffixingRandom$BaseNameResolver
        at org.hibernate.orm.core@5.4.0.Final/org.hibernate.cfg.Environment.buildBytecodeProvider(Environment.java:345)
        at org.hibernate.orm.core@5.4.0.Final/org.hibernate.cfg.Environment.buildBytecodeProvider(Environment.java:337)
        at org.hibernate.orm.core@5.4.0.Final/org.hibernate.cfg.Environment.<clinit>(Environment.java:230)
        at org.hibernate.orm.core@5.4.0.Final/org.hibernate.boot.registry.StandardServiceRegistryBuilder.<init>(StandardServiceRegistryBuilder.java:78)
        at org.hibernate.orm.core@5.4.0.Final/org.hibernate.boot.registry.StandardServiceRegistryBuilder.<init>(StandardServiceRegistryBuilder.java:67)
        at org.hibernate.orm.core@5.4.0.Final/org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.<init>(EntityManagerFactoryBuilderImpl.java:202)
        at org.hibernate.orm.core@5.4.0.Final/org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.<init>(EntityManagerFactoryBuilderImpl.java:174)
        at org.hibernate.orm.core@5.4.0.Final/org.hibernate.jpa.boot.spi.Bootstrap.getEntityManagerFactoryBuilder(Bootstrap.java:76)
        at org.hibernate.orm.core@5.4.0.Final/org.hibernate.jpa.HibernatePersistenceProvider.getEntityManagerFactoryBuilder(HibernatePersistenceProvider.java:171)
        at org.hibernate.orm.core@5.4.0.Final/org.hibernate.jpa.HibernatePersistenceProvider.getEntityManagerFactoryBuilderOrNull(HibernatePersistenceProvider.java:119)
        at org.hibernate.orm.core@5.4.0.Final/org.hibernate.jpa.HibernatePersistenceProvider.getEntityManagerFactoryBuilderOrNull(HibernatePersistenceProvider.java:61)
        at org.hibernate.orm.core@5.4.0.Final/org.hibernate.jpa.HibernatePersistenceProvider.createEntityManagerFactory(HibernatePersistenceProvider.java:50)
        at java.persistence@2.2/javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:79)
        at java.persistence@2.2/javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:54)
        at testing@1.0-SNAPSHOT/de.testing.Main.main(Main.java:9)
Caused by: java.lang.ClassNotFoundException: net.bytebuddy.NamingStrategy$SuffixingRandom$BaseNameResolver
        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)
        ... 15 more

However, when I remove

id 'org.openjfx.javafxplugin' version '0.0.5'

from my gradle plugin list and re-distribute the application, it runs fine using the distributed startscript from gradle:installDist.

José Pereda
  • 44,311
  • 7
  • 104
  • 132
C. Schmidt
  • 100
  • 1
  • 6

1 Answers1

1

The issue can be reproduced easily with the code you have posted.

I can confirm it is related to the javafx-gradle-plugin, which in turn uses the java9-modularity plugin. An issue has already been filed here a while ago, and it is probably related.

Basically the problem is that with this plugin all dependencies are added to the module path, and none of them are added to the classpath.

Why IDE works?

When you "run from IDE", I assume that you are running the run task:

./gradle run

And that works fine. If you do:

./gradle --info run

it will print the command line. Something like:

/Users/<user>/Downloads/jdk-11.0.1.jdk/Contents/Home/bin/java \
     --add-modules javafx.controls,javafx.fxml \
     --module-path  /path/to/your-project/build/classes/java/main: \
         /path/to/your-project/build/resources/main: \
         /Users/<user>/.gradle/caches/modules-2/files-2.1/org.hibernate/hibernate-core/5.4.0.Final/70...2b/hibernate-core-5.4.0.Final.jar: \
         ...
         /Users/<user>/.gradle/caches/modules-2/files-2.1/com.sun.xml.fastinfoset/FastInfoset/1.2.15/bb..e8/FastInfoset-1.2.15.jar \
     -Dfile.encoding=UTF-8 -Duser.variant \
     -cp /path/to/your-project/build/classes/java/main: \
         /path/to/your-project/build/resources/main: \
         /Users/<user>/.gradle/caches/modules-2/files-2.1/org.hibernate/hibernate-core/5.4.0.Final/70...2b/hibernate-core-5.4.0.Final.jar: \
         ...
         /Users/<user>/.gradle/caches/modules-2/files-2.1/com.sun.xml.fastinfoset/FastInfoset/1.2.15/bb..e8/FastInfoset-1.2.15.jar \
     de.testing.Main

Running from build/install/your-project/lib

So you could actually try to run manually from the lib folder you get from the installDist task with all the jars. From a terminal:

./gradlew installDist
cd build/install/your-project/lib
/Users/<user>/Downloads/jdk-11.0.1.jdk/Contents/Home/bin/java \
     --add-modules javafx.controls,javafx.fxml \
     --module-path . \
     -cp '*' \
     de.testing.Main

That should work.

The problem

So why running the script fails?

If you edit the script:

cd build/install/your-project/bin
nano your-project

you can verify that the line:

...
esac

CLASSPATH=

# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
...

contains an empty classpath. And if you see the final options:

# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $YOUR-PROJECT_OPTS -classpath "\"$CLASSPATH\"" de.testing.Main "$APP_ARGS"

this means that the final command line will be something like:

/Users/<user>/Downloads/jdk-11.0.1.jdk/Contents/Home/bin/java \
     --add-modules javafx.controls,javafx.fxml \
     --module-path . \
     -classpath \        // <--- Nothing is added here!
     de.testing.Main

That actually starts because all the jars have been added to the module-path. But fails with the posted exception because the classpath is empty.

Possible fixes

Manual fix

One possible fix is editing the script, after running the installDist task and adding the classpath like:

CLASSPATH="../lib/*"

Save the script and now run:

cd build/install/your-project/bin
./your-project

That should work.

Modify installDist task

Obviously, you won't do that manually, so here is a fix to the startScripts task that you can add to your build.gradle file:

tasks.startScripts { doLast { def scriptFile = file "${outputDir}/${applicationName}" scriptFile.text = scriptFile.text.replace('CLASSPATH=', 'CLASSPATH=\'$APP_HOME/lib/*\'') } }

Remove the plugin

Other possible solution is removing the plugin and use the old approach:

dependencies {
    compile group: 'org.hibernate', name: 'hibernate-core', version: '5.4.0.Final'
    compile group: 'com.h2database', name: 'h2', version: '1.4.197'

    compile "org.openjfx:javafx-base:11.0.1:mac"
    compile "org.openjfx:javafx-controls:11.0.1:mac"
    compile "org.openjfx:javafx-graphics:11.0.1:mac"
    compile "org.openjfx:javafx-fxml:11.0.1:mac"
}

compileJava {
    doFirst {
        options.compilerArgs = [
                '--module-path', classpath.asPath,
                '--add-modules', 'javafx.controls,javafx.fxml'
        ]
    }
}

run {
    doFirst {
        jvmArgs = [
                '--module-path', classpath.asPath,
                '--add-modules', 'javafx.controls,javafx.fxml'
        ]
    }
}

After running the installDist task, you can verify that the script contains a full classpath:

CLASSPATH=$APP_HOME/lib/your-project.jar:$APP_HOME/lib/hibernate-core-5.4.0.Final.jar:...:$APP_HOME/lib/FastInfoset-1.2.15.jar

Shadow jar

Another solution, keeping the plugin, but creating a shadow jar:

jar {
    manifest {
        attributes 'Main-Class': 'de.testing.Launcher'
    }
    from {
        configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
    }
}

where Launcher is a class that doesn't extend Application:

public class Launcher {

    public static void main(String[] args) {
        Main.main(args);
    }
}

Now you can run the jar task, and it will create a shadow jar with all the dependencies, including the JavaFX ones. Then you can run:

./gradlew jar
cd build/libs
java -jar your-project.jar

File an issue

Finally I'd suggest you file an issue here, linking this question.

José Pereda
  • 44,311
  • 7
  • 104
  • 132