javaeclipsemavenjdbcjlink

How do I add a missing dependency (JDBC DriverManager?) to custom JRE that gives SQLException?


I am using Maven inside Eclipse to build a modular JavaFX-App plus custom JRE from Red Hats JDK 17. I achieved this by following multiple online tutorials as well as the Maven and jlink documentation. Everything seems to be in order except the custom JRE is missing the JDBC DriverManager. At least that is my assumption because when I use the standard JRE the app works as intended.

I tried fiddling with the pom.xml and the module-info to no avail and after more than a day of trial-and-error I am at my wits end.

What do I have to do in order to be able to use the app with the custom JRE?

I'd appreciate it if someone has one or more useful tips or at least can nudge me in the right direction.

Using the standard JRE with the following command works fine:

java -jar myApp.jar

However, using the custom JRE with the following command...:

target\myapp_jre\bin\java.exe -jar myApp.jar

...leads to this error:

java.sql.SQLException: No suitable driver found for jdbc:oracle:thin:@ldap[...]

The Java-JRE versions are identical:

c:\dev\myApp>java -version
openjdk version "17.0.13" 2024-10-15 LTS
OpenJDK Runtime Environment (Red_Hat-17.0.13.0+11-1) (build 17.0.13+11-LTS)
OpenJDK 64-Bit Server VM (Red_Hat-17.0.13.0+11-1) (build 17.0.13+11-LTS, mixed mode, sharing)

c:\dev\myApp>target\myapp_jre\bin\java.exe -version
openjdk version "17.0.13" 2024-10-15 LTS
OpenJDK Runtime Environment (Red_Hat-17.0.13.0+11-1) (build 17.0.13+11-LTS)
OpenJDK 64-Bit Server VM (Red_Hat-17.0.13.0+11-1) (build 17.0.13+11-LTS, mixed mode)

I use this command to build the app and custom JRE in Eclipse:

clean package javafx:jlink

This is my module-info.java:

module MYAPP {
    exports de.ron.application;
    exports de.ron.csv_import_export;
    exports de.ron.databaseutils;
    exports de.ron.gui;
    exports de.ron.gui.dialogs;
    exports de.ron.tools;

    opens de.ron.application to javafx.fxml;
    opens de.ron.gui to javafx.fxml;
    opens de.ron.gui.dialogs to javafx.fxml;

    requires com.fasterxml.jackson.core;
    requires com.fasterxml.jackson.databind;

    requires java.desktop;
    requires java.base;
    requires transitive java.sql;

    requires javafx.fxml;
    requires transitive javafx.controls;
    requires transitive javafx.graphics;

    requires org.apache.commons.lang3;
    requires org.apache.logging.log4j;

    uses java.sql.Driver;
}

This is my pom.xml:

    <?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xml>
<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>de.ron</groupId>
    <artifactId>myApp</artifactId>
    <version>0.9.1</version>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.compiler>17</java.compiler>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <javaFxVersion>17.0.14</javaFxVersion>
        <jacksonXmlVersion>2.18.3</jacksonXmlVersion>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-controls</artifactId>
            <version>${javaFxVersion}</version>
        </dependency>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-fxml</artifactId>
            <version>${javaFxVersion}</version>
        </dependency>
        <dependency>
            <groupId>com.oracle.database.jdbc</groupId>
            <artifactId>ojdbc11</artifactId>
            <version>23.7.0.25.01</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.17.0</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>${jacksonXmlVersion}</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>${jacksonXmlVersion}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.24.3</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.14.0</version>
                    <configuration>
                        <release>17</release>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-dependency-plugin</artifactId>
                <version>3.8.1</version>
                <executions>
                    <execution>
                        <id>copy-dependencies</id>
                        <phase>prepare-package</phase>
                        <goals>
                            <goal>copy-dependencies</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${project.build.directory}/libs</outputDirectory>
                            <overWriteReleases>false</overWriteReleases>
                            <overWriteSnapshots>false</overWriteSnapshots>
                            <overWriteIfNewer>true</overWriteIfNewer>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>3.7.1</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>${project.basedir}</outputDirectory>
                            <archive>
                                <manifest>
                                    <mainClass>de.ron.application.Main</mainClass>
                                </manifest>
                            </archive>
                            <descriptorRefs>
                                <descriptorRef>jar-with-dependencies</descriptorRef>
                            </descriptorRefs>
                            <finalName>myApp</finalName>
                            <appendAssemblyId>false</appendAssemblyId>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.openjfx</groupId>
                <artifactId>javafx-maven-plugin</artifactId>
                <version>0.0.8</version>
                <configuration>
                    <stripDebug>true</stripDebug>
                    <compress>2</compress>
                    <noHeaderFiles>true</noHeaderFiles>
                    <noManPages>true</noManPages>
                    <launcher>myapp</launcher>
                    <jlinkImageName>myapp_jre</jlinkImageName>
                    <mainClass>MYAPP/de.ron.application.Main</mainClass>
                    <runtimePathOption>MODULEPATH</runtimePathOption>
                    <options>
                        <option>--module-path</option>
                        <option>${project.build.directory}/libs</option>
                    </options>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Finally, all libraries seem to be where they're supposed to be. At least on file system level. See the following images for reference.

assambly_libs_folder

jar_contents

custom_jre_legal_folder_content

--------UPDATE--------

First, I switched to ojdbc17. However, as with ojdbc11, when I try to load the DriverManager manually as suggested by @MarkRotteveel in the comments with this line

Class.forName("oracle.jdbc.OracleDriver");

I get the following ClassNotFoundException.

Exception in Application constructor Exception in thread "main" java.lang.RuntimeException: Unable to construct Application instance: class de.ron.application.MainGuiStarter at javafx.graphics/com.sun.javafx.application.LauncherImpl.launchApplication1(Unknown Source) at javafx.graphics/com.sun.javafx.application.LauncherImpl.lambda$launchApplication$2(Unknown Source) at java.base/java.lang.Thread.run(Unknown Source) Caused by: java.lang.reflect.InvocationTargetException at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(Unknown Source) at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(Unknown Source) at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Unknown Source) at java.base/java.lang.reflect.Constructor.newInstance(Unknown Source) at javafx.graphics/com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$8(Unknown Source) at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runAndWait$12(Unknown Source) at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(Unknown Source) at java.base/java.security.AccessController.doPrivileged(Unknown Source) at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$11(Unknown Source) at javafx.graphics/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(Unknown Source) at javafx.graphics/com.sun.glass.ui.win.WinApplication._runLoop(Native Method) at javafx.graphics/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(Unknown Source) ... 1 more Caused by: java.lang.NoClassDefFoundError: javax/management/InstanceAlreadyExistsException at java.base/java.lang.Class.forName0(Native Method) at java.base/java.lang.Class.forName(Unknown Source) at MYAPP@0.9.1/de.ron.databaseutils.DatabaseUtils.loadJdbcDriver(Unknown Source) at MYAPP@0.9.1/de.ron.application.MainAppController.updateDatabaseLibraryMainGui(Unknown Source) at MYAPP@0.9.1/de.ron.gui.MainGuiController.updateDatabase(Unknown Source) at MYAPP@0.9.1/de.ron.gui.MainGuiController.(Unknown Source) at MYAPP@0.9.1/de.ron.application.MainGuiStarter.(Unknown Source) ... 14 more Caused by: java.lang.ClassNotFoundException: javax.management.InstanceAlreadyExistsException at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(Unknown Source) at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(Unknown Source) at java.base/java.lang.ClassLoader.loadClass(Unknown Source) ... 21 more

The class is present in the JAR at the given location. Nevertheless, it does not work and I also did not find any suitable solution to that problem online. Adding to my confusion is the fact that either @MarkRotteveel or @BasilBourque wrote in one of thier multiple contributions to similar problems that using Class.forName() should not be used. And to be honest, I didn't like the idea either when I first read it, because manually loading classes at runtime seems a bit "dirty" to me. ^_^° (I write that knowing that it is the legacy way of loading the driver.)

Anyway, are there any other tipps concerning the ClassNotFoundException, how to get the DriverManager to work, or should I switch to a different way of connecting to the database altogether in order to be able to use my custom JRE?


Solution

  • Some additional research and debugging of my app after reading @MarkRotteveel's "java.management"-comment, led me to this post with these comments by @life888888 . Turns out that adding the java.management-module resolved the exception posted in my update.

    However, after that, I got a NamingException which got resolved by adding the java.naming-module to the module-info.java.

    Caused by: java.lang.NoClassDefFoundError: javax/naming/NamingException
    

    But this lead to a GSSEexception:

    Caused by: java.lang.NoClassDefFoundError: org/ietf/jgss/GSSException
    

    I added the java.security.jgss-module to the module-info.java accordingly.

    In my case, the JGSS-Module is needed because I utilize javafx.concurrent.Task to make my database queries.

    With all the right modules in place, I also don't need to load the DriverManager manually via Class.forName() as suggested by @MarkRotteveel in this comment.

    My functioning module-info.java:

    
    module MYAPP {
        exports de.ron.application;
        exports de.ron.csv_import_export;
        exports de.ron.databaseutils;
        exports de.ron.gui;
        exports de.ron.gui.dialogs;
        exports de.ron.tools;
    
        opens de.ron.application to javafx.fxml;
        opens de.ron.gui to javafx.fxml;
        opens de.ron.gui.dialogs to javafx.fxml;
    
        requires com.fasterxml.jackson.core;
        requires com.fasterxml.jackson.databind;
    
        requires java.desktop;
        requires java.base;
        requires java.management;
        requires java.naming;
        requires java.security.jgss;
        requires transitive java.sql;
    
        requires javafx.fxml;
        requires transitive javafx.controls;
        requires transitive javafx.graphics;
    
        requires org.apache.commons.lang3;
        requires org.apache.logging.log4j;
    }
    

    Thanks for helping me solve this problem. I appreciate it very much.