Search code examples
javamariadbmaven-3mysql-connector

JDBC Driver not found in packaged JavaFX "jlink" app


I have installed MariaDB-JDBC-Java Driver V 3.3.3 from maven repo. When I issue mvn clean javafx:jlink the resulting Linux app does not start with an error, not finding the Driver:

java.lang.ClassNotFoundException: org.mariadb.jdbc.Driver

The command mvn clean javafx:run works just fine in Linux and Windows.

Please tell me why mvn target "javafx:jlink" is broken as soon as I write the dependency of the mariadb java connector into pom.xml?

I tried to substitue the mariadb java client with the mysql java connector in pom.xml and it has exactly the same behaviour.

I expected the packaged java app to connect via driver to the mariadb container as it is successfully doing on cmd line and IntelliJ IDEA - both Windows and Arch Linux.

-----------------module-info.java

module org.leder.gldisplaymanager {
    requires javafx.controls;
    requires javafx.fxml;
    requires java.sql;


    opens org.leder.gldisplaymanager to javafx.fxml;
    exports org.leder.gldisplaymanager;
    exports org.leder.gldisplaymanager.gui;
    opens org.leder.gldisplaymanager.gui to javafx.fxml;

    uses java.sql.Driver;
}

UPDATE: I compiled a minimal example for IntelliJ project import and Linux app execution as follows -

expected result: java.sql.SQLNonTransientConnectionException: Socket fail to connect to host:address=(host=localhost)(port=3306)(type=primary). Verbindungsaufbau abgelehnt

actual result: java.sql.SQLException: No suitable driver found for jdbc:mariadb://localhost/display_manager

intelliJ_project and Linux_app

UPDATE2:

pom.xml dependency is as follows:

        <dependency>
            <groupId>org.mariadb.jdbc</groupId>
            <artifactId>mariadb-java-client</artifactId>
            <version>3.3.3</version>
        </dependency>

Solution

  • Here is a partially manual, imperfect solution that can still be executed.

    Steps:

    (1) open your intelliJ_project and Linux_app

    (2) download mariadb.zip

    (3) unzip mariadb.zip

    (4) cd mariadb

    (5) mvn package

    generate output -> target/mariadb-1.0-SNAPSHOT.jar

    (6) mvn dependency:copy-dependencies -DoutputDirectory=target/libs

    generate -> target/libs include all jar files in this directory

    (7) mvn javafx:jlink

    generate -> target/app directory

    (8) execute commands

    cp target/libs/mariadb-java-client-3.3.3.jar target/app/lib/

    cp target/libs/caffeine-2.9.3.jar target/app/lib/

    cp target/libs/checker-qual-3.32.0.jar target/app/lib/

    cp target/libs/error_prone_annotations-2.10.0.jar target/app/lib/

    cp target/libs/jcl-over-slf4j-2.0.7.jar target/app/lib/

    cp target/libs/jna-5.13.0.jar target/app/lib/

    cp target/libs/jna-platform-5.13.0.jar target/app/lib/

    cp target/libs/slf4j-api-2.0.7.jar target/app/lib/

    cp target/libs/waffle-jna-3.3.0.jar target/app/lib/

    cp target/app/bin/app target/app/bin/app.org

    Copy MariaDB jar files to arget/app/lib/

    • mariadb-java-client-3.3.3.jar
    • caffeine-2.9.3.jar
    • checker-qual-3.32.0.jar
    • error_prone_annotations-2.10.0.jar
    • jcl-over-slf4j-2.0.7.jar
    • jna-5.13.0.jar
    • jna-platform-5.13.0.jar
    • slf4j-api-2.0.7.jar
    • waffle-jna-3.3.0.jar

    (9) edit target/app/bin/app file

    #!/usr/bin/env sh
    
    ##############################################################################
    ##
    ##  hellofx start up script for UN*X
    ##
    ##############################################################################
    
    # Attempt to set APP_HOME
    # Resolve links: $0 may be a link
    PRG="$0"
    # Need this for relative symlinks.
    while [ -h "$PRG" ] ; do
        ls=`ls -ld "$PRG"`
        link=`expr "$ls" : '.*-> \(.*\)$'`
        if expr "$link" : '/.*' > /dev/null; then
            PRG="$link"
        else
            PRG=`dirname "$PRG"`"/$link"
        fi
    done
    SAVED="`pwd`"
    cd "`dirname \"$PRG\"`/.." >/dev/null
    APP_HOME="`pwd -P`"
    cd "$SAVED" >/dev/null
    
    APP_NAME="hellofx"
    APP_BASE_NAME=`basename "$0"`
    
    # Add default JVM options here. You can also use JAVA_OPTS and HELLOFX_OPTS to pass JVM options to this script.
    DEFAULT_JVM_OPTS=""
    
    # Use the maximum available, or set MAX_FD != -1 to use that value.
    MAX_FD="maximum"
    
    warn () {
        echo "$*"
    }
    
    die () {
        echo
        echo "$*"
        echo
        exit 1
    }
    
    # OS specific support (must be 'true' or 'false').
    cygwin=false
    msys=false
    darwin=false
    nonstop=false
    case "`uname`" in
      CYGWIN* )
        cygwin=true
        ;;
      Darwin* )
        darwin=true
        ;;
      MINGW* )
        msys=true
        ;;
      NONSTOP* )
        nonstop=true
        ;;
    esac
    
    CLASSPATH="$APP_HOME/lib/*"
    
    JAVA_HOME="$APP_HOME"
    JAVACMD="$JAVA_HOME/bin/java"
    
    # Increase the maximum file descriptors if we can.
    if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
        MAX_FD_LIMIT=`ulimit -H -n`
        if [ $? -eq 0 ] ; then
            if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
                MAX_FD="$MAX_FD_LIMIT"
            fi
            ulimit -n $MAX_FD
            if [ $? -ne 0 ] ; then
                warn "Could not set maximum file descriptor limit: $MAX_FD"
            fi
        else
            warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
        fi
    fi
    
    # For Darwin, add options to specify how the application appears in the dock
    if $darwin; then
        GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
    fi
    
    # For Cygwin, switch paths to Windows format before running java
    if $cygwin ; then
        APP_HOME=`cygpath --path --mixed "$APP_HOME"`
        CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
        JAVA_HOME="$APP_HOME"
        JAVACMD="$JAVA_HOME/bin/java"
    
        # We build the pattern for arguments to be converted via cygpath
        ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
        SEP=""
        for dir in $ROOTDIRSRAW ; do
            ROOTDIRS="$ROOTDIRS$SEP$dir"
            SEP="|"
        done
        OURCYGPATTERN="(^($ROOTDIRS))"
        # Add a user-defined pattern to the cygpath arguments
        if [ "$GRADLE_CYGPATTERN" != "" ] ; then
            OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
        fi
        # Now convert the arguments - kludge to limit ourselves to /bin/sh
        i=0
        for arg in "$@" ; do
            CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
            CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
    
            if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
                eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
            else
                eval `echo args$i`="\"$arg\""
            fi
            i=$((i+1))
        done
        case $i in
            (0) set -- ;;
            (1) set -- "$args0" ;;
            (2) set -- "$args0" "$args1" ;;
            (3) set -- "$args0" "$args1" "$args2" ;;
            (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
            (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
            (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
            (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
            (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
            (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
        esac
    fi
    
    # Escape application args
    save () {
        for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
        echo " "
    }
    APP_ARGS=$(save "$@")
    
    
    
    # Collect all arguments for the java command, following the shell quoting and substitution rules
    eval set -- $DEFAULT_JVM_OPTS $CDS_JVM_OPTS $JAVA_OPTS $HELLOFX_OPTS -classpath "\"$CLASSPATH\"" org.leder.mariadb.HelloApplication "$APP_ARGS"
    
    # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
    if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
      cd "$(dirname "$0")"
    fi
    
    exec "$JAVACMD" "$@"
    

    (10) Test Run

    ./target/app/bin/app

    you will get expected result: java.sql.SQLNonTransientConnectionException: Socket fail to connect to host:address=(host=localhost)(port=3306)(type=primary).

    UPDATE - AUTOMATION in MAVEN

    modify your pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>org.leder</groupId>
        <artifactId>mariadb</artifactId>
        <version>1.0-SNAPSHOT</version>
        <name>mariadb</name>
    
        <properties>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <junit.version>5.10.0</junit.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.openjfx</groupId>
                <artifactId>javafx-controls</artifactId>
                <version>17.0.6</version>
            </dependency>
            <dependency>
                <groupId>org.openjfx</groupId>
                <artifactId>javafx-fxml</artifactId>
                <version>17.0.6</version>
            </dependency>
    
            <dependency>
                <groupId>org.junit.jupiter</groupId>
                <artifactId>junit-jupiter-api</artifactId>
                <version>${junit.version}</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.junit.jupiter</groupId>
                <artifactId>junit-jupiter-engine</artifactId>
                <version>${junit.version}</version>
                <scope>test</scope>
            </dependency>
            <dependency>
                <groupId>org.mariadb.jdbc</groupId>
                <artifactId>mariadb-java-client</artifactId>
                <version>3.3.3</version>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.11.0</version>
                    <configuration>
                        <source>17</source>
                        <target>17</target>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>org.openjfx</groupId>
                    <artifactId>javafx-maven-plugin</artifactId>
                    <version>0.0.8</version>
                    <executions>
                        <execution>
                            <!-- Default configuration for running with: mvn clean javafx:run -->
                            <id>default-cli</id>                        
                            <configuration>
                                <mainClass>org.leder.mariadb/org.leder.mariadb.HelloApplication</mainClass>
                                <launcher>app</launcher>
                                <jlinkZipName>app</jlinkZipName>
                                <jlinkImageName>app</jlinkImageName>
                                <noManPages>true</noManPages>
                                <stripDebug>true</stripDebug>
                                <noHeaderFiles>true</noHeaderFiles>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
                 <plugin>
                <artifactId>maven-dependency-plugin</artifactId>
                <executions>
                  <execution>
                    <phase>compile</phase>
                    <goals>
                      <goal>copy-dependencies</goal>
                    </goals>
                    <configuration>
                      <outputDirectory>${project.build.directory}/libs</outputDirectory>
                    </configuration>
                  </execution>
                </executions>
              </plugin>
              
     <plugin>
            <groupId>com.coderplus.maven.plugins</groupId>
            <artifactId>copy-rename-maven-plugin</artifactId>
            <version>1.0</version>
            <executions>
              <execution>
                <id>default-cli</id>
                <phase>package</phase>
                <goals>
                  <goal>copy</goal>
                </goals>
                <configuration>
                  <fileSets>
                    <fileSet>
                      <sourceFile>${project.build.directory}/libs/caffeine-2.9.3.jar</sourceFile>
                      <destinationFile>${project.build.directory}/app/lib/caffeine-2.9.3.jar</destinationFile>
                    </fileSet>
                    
    
                    <fileSet>
                      <sourceFile>${project.build.directory}/libs/checker-qual-3.32.0.jar</sourceFile>
                      <destinationFile>${project.build.directory}/app/lib/checker-qual-3.32.0.jar</destinationFile>
                    </fileSet>
    
    
                    <fileSet>
                      <sourceFile>${project.build.directory}/libs/error_prone_annotations-2.10.0.jar</sourceFile>
                      <destinationFile>${project.build.directory}/app/lib/error_prone_annotations-2.10.0.jar</destinationFile>
                    </fileSet>
                    
                    <fileSet>
                      <sourceFile>${project.build.directory}/libs/jcl-over-slf4j-2.0.7.jar</sourceFile>
                      <destinationFile>${project.build.directory}/app/lib/jcl-over-slf4j-2.0.7.jar</destinationFile>
                    </fileSet>
                    
                    
                    <fileSet>
                      <sourceFile>${project.build.directory}/libs/jna-5.13.0.jar</sourceFile>
                      <destinationFile>${project.build.directory}/app/lib/jna-5.13.0.jar</destinationFile>
                    </fileSet>
    
                    <fileSet>
                      <sourceFile>${project.build.directory}/libs/jna-platform-5.13.0.jar</sourceFile>
                      <destinationFile>${project.build.directory}/app/lib/jna-platform-5.13.0.jar</destinationFile>
                    </fileSet>
    
    
                    <fileSet>
                      <sourceFile>${project.build.directory}/libs/slf4j-api-2.0.7.jar</sourceFile>
                      <destinationFile>${project.build.directory}/app/lib/slf4j-api-2.0.7.jar</destinationFile>
                    </fileSet>
                    
                    <fileSet>
                      <sourceFile>${project.build.directory}/libs/waffle-jna-3.3.0.jar</sourceFile>
                      <destinationFile>${project.build.directory}/app/lib/waffle-jna-3.3.0.jar</destinationFile>
                    </fileSet>
                    
                    
                    <fileSet>
                      <sourceFile>${project.build.directory}/libs/mariadb-java-client-3.3.3.jar</sourceFile>
                      <destinationFile>${project.build.directory}/app/lib/mariadb-java-client-3.3.3.jar</destinationFile>
                    </fileSet>
                    <fileSet>
                      <sourceFile>${project.basedir}/src/main/dist/app</sourceFile>
                      <destinationFile>${project.build.directory}/app/bin/app</destinationFile>
                    </fileSet>             
                  </fileSets>
                </configuration>
              </execution>
            </executions>
          </plugin>
            </plugins>
        </build>
    </project>
    

    add app file in your project folder (9)

    reference step: (9) edit target/app/bin/app file <-- copy this file

    (a) create dir dist under mariadb/src/main

    (b) put app in (a)

    (c) path mariadb/src/main/dist/app

    Run in one command

    mvn clean javafx:jlink com.coderplus.maven.plugins:copy-rename-maven-plugin:1.0:copy

    Test

    ./target/app/bin/app

    You can do it in one mvn command, you can skip Steps: (1) ~ (9)

    Summary

    (1) app contents is modify from Gradle pluginThe Badass Runtime Plugin。 I am reference from https://github.com/beryx-gist/badass-runtime-example-javafx (beryx-gist / badass-runtime-example-javafx Public)

    (2) This answer is not good enough.  If you already know how to perform the manual steps, you can rewrite the above content into an automated Maven configuration.

    (3) final app dir

    app
    ├── bin
    │   ├── app     (New app file)
    │   ├── app.org (original app file)
    ...
    ├── lib
    │   ├── caffeine-2.9.3.jar
    │   ├── checker-qual-3.32.0.jar
    │   ├── error_prone_annotations-2.10.0.jar
    │   ├── jcl-over-slf4j-2.0.7.jar
    │   ├── jna-5.13.0.jar
    │   ├── jna-platform-5.13.0.jar
    │   ├── slf4j-api-2.0.7.jar
    │   ├── waffle-jna-3.3.0.jar
    │   ├── mariadb-java-client-3.3.3.jar
    ...