Search code examples
javajarclasspath

NoClassDefFoundError for dependencies when compiling executable jar with no build tools


I'm trying to compile my Java program into an executable jar file, and I've tried following all the instructions in various places, but I'm encountering an issue.

Class Files

The first thing I do is compile everything to class files with this command.

javac -d ./build src/main/**/*.java -cp lib/main/commons-cli-1.4/commons-cli-1.4.jar:lib/main/commons-io-2.8.0/commons-io-2.8.0.jar

This compiles all the .java files in src/main (I don't want to compile the tests of course) into a folder called build. It also sets the classpath to all the .jar files that the code depends on (Apache Commons CLI and Apache Commons IO).

Jar Files

The next step is to generate the .jar file. For this, I use the following command (after cd ./build).

jar cvfm ../bin/program.jar manifest.txt .

Here, manifest.txt is in the folder build and contains the following. (It ends with a trailing newline, as per the documentation dictates.)

Main-Class: cli.Cli

Here, cli.Cli refers to the class named Cli located in src/main/cli/Cli.java which contains the main method.

Running the Jar

Now I use the following command to try to run the produced program.jar file, but I get this error.

$ java -jar build/program.jar
Error: Unable to initialize main class cli.Cli
Caused by: java.lang.NoClassDefFoundError: org/apache/commons/cli/ParseException

This error is referring to one of the import statements in src/main/cli/Cli.java, which is supposed to be in the same .jar file as all the other import statements. The IDE does recognise the import, but I'm not sure why I am unable to run the file. Here is an extract of my imports from Cli.java.

package cli;

import java.io.IOException;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import org.apache.commons.io.FilenameUtils;
// more imports...

Edit

I tried to change the manifest to contain a classpath, but that didn't seem to help at all. I tried the following two versions of the manifest.

Relative to bin/ or build/:

Main-Class: cli.Cli
Class-Path: ../lib/main/commons-cli-1.4/commons-cli-1.4.jar:../lib/main/commons-io-2.8.0/commons-io-2.8.0.jar

Relative to workspace folder (which is the current directory when I run the jar):

Main-Class: cli.Cli
Class-Path: lib/main/commons-cli-1.4/commons-cli-1.4.jar:lib/main/commons-io-2.8.0/commons-io-2.8.0.jar

Edit 2

I changed my code to not use ParseException but then another of the imports caused the same error.


Solution

  • Turns out the classpath in the manifest needed to be space-separated, with the paths to each jar relative to bin/, like so:

    Main-Class: cli.Cli
    Class-Path: ../lib/main/commons-cli-1.4/commons-cli-1.4.jar ../lib/main/commons-io-2.8.0/io-2.8.0.jar program.jar