How can I make my Apache Derby database work when I run my generated jar file?

Viewed 164

I have created a JavaFX application and have a generated Jar file (generated with Gradle) that will launch.

When the application does launch, it doesn't connect to the embedded database though. I feel as though I am missing something very simple but after a lot of research, I haven't been able to figure it out. When running the jar file from the command prompt, I get the following error: java.lang.ClassNotFoundException: org.apache.derby.jdbc.EmbeddedDriver

From the reading I have done, I understand I may be able to add this to my classpath but I have not been successful with this after multiple attempts and I have made this application to be run on another computer. This is possible, right?

If possilbe, I would love to change something in my build.gradle file or surround the .jar in a folder or something like that that would make this simple for the person running the program. Program size is not a concern short of multiple gigabytes.

2 Answers

You've got two problems

  • The driver class is not in your app jar
  • If you embed that db into the app jar, you're not going to be able to write to it

You can make a 'fat jar' but the isn't going to solved the second problem. You really need to make an installer to do this, in order to leave the db in the file system, so it can be written to.

Problem

Part of what Gradle does is dependency management. That means it knows what dependencies you need and how to find them (based on configurations in the build script). When you execute/build your application via Gradle the tool will automatically search repositories, download+cache dependencies, and place those dependencies on the class-path/module-path. Once you deploy your JAR file Gradle is no longer involved, so your deployment is responsible for including the needed dependencies.

In other words, you need to ship your application's dependencies with your application JAR file.


Solutions

You basically just need to make sure you include your application's dependencies with your application. Here are at least three ways to do that.

Copy Dependencies

Copy the dependencies into a build folder as part of the build process. Here's an example of such a task, using the Kotlin DSL:

tasks {
    val jar by existing(Jar::class)

    val copyDependencies by registering(Copy::class) {
        from(configurations.runtimeClasspath)
        into(jar.get().destinationDirectory)
    }

    jar.configure {
        finalizedBy(copyDependencies)
    }
}

Now if you execute ./gradlew jar Gradle will create the JAR file and then copy the dependencies into the same directory as the JAR file. Then you just need to make sure all the JAR files are deployed together.

If I remember correctly, the default class-path is the working directory. But to specify the class-path you would use -cp, -classpath, or --class-path when executing your application. The module-path, if needed, is set with -p or --module-path.

Fat JAR

Create a so-called "fat" or "uber" JAR file. That's a JAR file that includes not just your own application code but all your application's dependencies as well. You could configure the jar task for this, but it would probably be easier for you to simply apply the Gradle Shadow Plugin.

// Kotlin DSL
plugins {
    id("com.github.johnrengelman.shadow") version "<version>"
    // other plugins...
}

And then you'd create the fat JAR with ./gradlew shadowJar. See the user guide for more information.

Self-Contained Application

Create a self-contained executable using a tool like jpackage. This tool gives you an application that has all its code and the JRE embedded, and then gives you an installer or native executable (e.g. exe on Windows). Here's the user guide for jpackage. There are Gradle plugins to make using jpackage from Gradle easier, such as The Badass JLink Plugin.

Note jpackage was added in Java 14 and was incubating until Java 16. Also note that jpackage can't "cross-package". That is to say, if you build your application on Windows then you can only create installers/executables for Windows; same for MacOS and Linux. If you need to package for multiple platforms then you'll need access to each platform.


JavaFX

Since you've tagged this question with JavaFX I want to give a note of caution. Though if you're not using JavaFX 9+ then this is not relevant to you.

Technically JavaFX only supports being loaded as named modules. That means it needs to be placed on the module-path, either via --module-path or by including it in the custom-runtime image built by jlink / jpackage. As of JavaFX 16 a warning is now emitted if JavaFX is loaded from unnamed modules (i.e. the class-path).

Executable JAR files are placed on the class-path. That includes fat JARs. And if you are not using a JDK that includes JavaFX—meaning you have Gradle pull in the JavaFX dependencies—then JavaFX will be included in your fat JAR and be placed on the class-path. Now, despite not being supported and now emitting a warning, nothing seems to currently break if JavaFX is on the class-path. Except for one caveat: Your main class must not be a subclass of javafx.application.Application. You'd have to create a separate main class that simply launches JavaFX.

Because of all this, I would highly recommend using jpackage to deploy JavaFX applications. You may want to read this Q&A as well.

Related