As a result of this question
java.lang.NoClassDefFoundError with explicit CLASSPATH
It has emerged me a doubt. The Oracle documentation onCLASSPATH
tells us:
The class path tells JDK tools and applications where to find third-party and user-defined classes -- that is, classes that are not Java extensions or part of the Java platform. The class path needs to find any classes you've compiled with the javac compiler -- its default is the current directory to conveniently enable those classes to be found ... Class paths to the .jar, .zip or .class files. Each classpath should end with a filename or directory depending on what you are setting the class path to:
- For a .jar or .zip file that contains .class files, the class path ends with the name of the .zip or .jar file.
- For .class files in an unnamed package, the class path ends with the directory that contains the .class files.
- For .class files in a named package, the class path ends with the directory that contains the "root" package (the first package in the full package name).
Multiple path entries are separated by semi-colons.
However, from my experience, I have come to the conclusion that all the tutorials on the internet and the documentation itself are wrong .
The concrete case.
Let's put a minimal code file, which uses some external library:
import com.google.gson.Gson;
public class MCVE {
public static void main( String [] args ) {
System.out.println( " ***** Iniciando la prueba *****" );
MCVE mcve = new MCVE( );
String json = new Gson( ).toJson( mcve );
System.out.println( json );
System.out.println( " ***** Prueba terminada *****" );
}
}
Likewise, a pom.xml
minimum, but that we copy the dependencies to a directory (in this case, target/libs
):
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>es.stackoverflow.com</groupId>
<artifactId>MCVE</artifactId>
<version>1.0</version>
<properties>
<maven.compiler.target>8</maven.compiler.target>
<maven.compiler.source>8</maven.compiler.source>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.test.skip>true</maven.test.skip>
</properties>
<dependencies>
<dependency>
<!-- https://mvnrepository.com/artifact/com.google.code.gson/gson -->
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>MCVE</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/libs</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
All this placed in a more or less standard directory hierarchy:
/mcve
+-pom.xml
+-src
+-main
+-java
+-MCVE.java
We compile it:
>mvn package
After which, our directory hierarchy contains:
/mcve
+-pom.xml
+-src/
| +-main/
| +-java/
| +-MCVE.java
+-target/
+-MCVE-1.0.jar
+-libs/
+-gson-2.8.5.jar
Also, we look inside the .jar
, the file MANIFEST.MF
:
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: root
Created-By: Apache Maven 3.6.3
Build-Jdk: 1.8.0_252-ea
Main-Class: MCVE
Now, we run it:
>cd target
>CLASSPATH='libs/' java -jar MCVE-1.0.jar
But it seems that he doesn't like it :
>java -jar MCVE-1.0.jar
***** Starting test *****
Exception in thread "main" java.lang.NoClassDefFoundError: com/google/gson/Gson
at MCVE.main(MCVE.java: 9)
Caused by: java.lang.ClassNotFoundException: com.google.gson.Gson
at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
at java.lang.ClassLoader.loadClass(ClassLoader.java:418)
at sun .misc.Launcher$AppClassLoader.loadClass(Launcher.java:352)
at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
... 1 more
We continue testing all possible ways to indicate the CLASSPATH
:
>CLASSPATH='libs' java -jar MCVE-1.0.jar
>CLASSPATH='/root/mcve/target/libs/' java -jar MCVE-1.0.jar
>CLASSPATH='/root/mcve/target/libs' java -jar MCVE-1.0.jar
>export CLASSPATH='/root/mcve/target/libs/'; java -jar MCVE-1.0.jar
>CLASSPATH='libs/gson-2.8.5.jar' java -jar MCVE-1.0.jar
>CLASSPATH='/root/mcve/target/libs/gson-2.8.5.jar' java -jar MCVE-1.0.jar
...
>java -cp 'libs/' -jar MCVE-1.0.jar
>java -cp 'libs/' -jar MCVE-1.0.jar
>java -cp '/root/mcve/target/libs/gson-2.8.5.jar' -jar MCVE-1.0.jar
...
In all cases I get the same exception.
The problem is the use of the command
java
with incompatible parameters: When executing a jar, you have put-jar
, but if we see the documentation of the command for that parameter we find the following:(bolds are mine)
If you want to run a jar with dependencies, you have two options:
Add the dependencies on the classpath in the MANIFEST.MF file:
Add the jar with the Main class to the classpath, using
-cp
instead of-jar
. This causes the Manifest file to be ignored, so you'll have to explicitly declare the class that has the methodmain
: