Search code examples
javagradlejavafxjavafx-11openjfx

Can't run JavaFX application outside IDE when using javafxplugin and hibernate


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.


Solution

  • 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.