Dev Learning Lab

The labs on this site need JavaScript to run. The same content is written out below.

Fundamentals › Java

Libraries and dependencies

What is a library, and what happens when it is missing?

A library is a set of classes that someone else has made. In this lab, one class, lib.Greeter, is packed into a jar file (a file that bundles class files) named greet.jar. My code, App.java, uses that class with import lib.Greeter;, but Greeter is not inside App.java. So both when compiling and when running, the compiler (javac) and the JVM (java) have to find Greeter on the class path (a list of places to look for class files), and in the command you write that list after -cp. The libraries that my project uses are called dependencies, and in the build tool Gradle, a dependency declared with implementation goes into the class path for both compiling and running. In this lab you take greet.jar out of the javac command and the java command one at a time, and the real compile and run happen, to show which error occurs at which step. (Gradle is not run in this lab.)

Checked against real execution · Built greet.jar on JDK 21 and ran the javac and java commands exactly as shown in 4 combinations, then checked the results against them

Concepts in this lab

Libraries and JAR files
A library is a set of classes that someone else has made, which my code uses with import. In Java it is common to bundle compiled class files into one jar file and hand it out. For example, if you compile Greeter.java and bundle it with the jar tool, jar tf greet.jar shows the list of files inside, including lib/Greeter.class. When you compile or run my code and need a class inside the jar, you have to write that jar on the class path. Being in the same folder as my source does not put the jar among the places to look.
Class path (-cp)
The class path is the list of places where the compiler (javac) and the JVM (java) look for user class files. In the command you write it after -cp (or --class-path, -classpath), and each place in the list is a directory or a JAR file, separated by a semicolon (;) on Windows and a colon (:) on other systems. If you do not write -cp and there is no CLASSPATH environment variable, both javac and java use the current folder. javac and java each receive the class path of their own command separately, so the -cp you gave when compiling does not carry over by itself to the run. If you write only a jar, as in -cp greet.jar, the current folder is not among the places to look, so to use the class files in the current folder too, you have to write the dot (.) with it, as in greet.jar;.
NoClassDefFoundError
NoClassDefFoundError is an error thrown when the JVM or a class loader tries to load the definition of a class and cannot find that definition. The API documentation describes it as the case where the definition of the class being looked for existed when the currently running class was compiled but can no longer be found. It occurs if you put a library jar on the class path when compiling and leave it out when running. It differs in timing from a compile error such as "package lib does not exist", which occurs when the jar is missing at compile time. The compiler reports a compile error before the program runs, and this error occurs while the program is running, at the place where that class becomes needed.
Dependencies and implementation
A dependency is a library that my project uses. In Gradle you write it inside the dependencies block as group:name:version coordinates. Among the places to write them, implementation is where you write a dependency that is needed both when compiling and when running the main source. According to the Gradle documentation, a dependency declared with implementation is included in both compileClasspath, used for compiling, and runtimeClasspath, used for running. That is, the job of writing -cp by hand in the javac and java commands is done by the build tool with one line of configuration.

Try these

Running with the default settings

  1. When you type the command javac -cp greet.jar App.java in the terminal, the compiler (javac) reads App.java. The import lib.Greeter; line at the top of the source means that it uses the class Greeter, which is not inside App.java. Greeter is a class that someone else made, and it is inside greet.jar as a file named lib/Greeter.class.
  2. The compiler has to know what kind of class Greeter is before it can compile, so it looks for lib/Greeter.class on the class path. The class path is a list of places to look for class files, and you write it after -cp in the command. This command says -cp greet.jar, so the place to look is greet.jar.
  3. It found lib/Greeter.class inside greet.jar. The compiler now knows the class Greeter, so the Greeter.hello("kim") line passes too.
  4. The compile succeeds and App.class is created. The only new class file in the folder is App.class, and Greeter.class was not copied. The contents of greet.jar did not come inside App.class.
  5. When you type the command java -cp greet.jar;. App, the JVM (java) looks for the class App on the class path and starts main. The class path is "greet.jar;." The semicolon (;) is the character that separates several places on Windows (it is a colon, :, on macOS and Linux), and the dot (.) at the end is the current folder, that is, the place where App.class is. If you leave out the dot and write only -cp greet.jar, it does not look in the current folder and cannot find App.
  6. To call Greeter.hello on the line System.out.println(Greeter.hello("kim")) inside main, the class Greeter is needed. The JVM looks for lib/Greeter.class on the class path.
  7. It finds lib/Greeter.class inside greet.jar and loads it. hello("kim") returns hello, kim, and println prints that text.

Result of this run

Code

# App.java
// Import a class made by someone else, which is inside greet.jar
import lib.Greeter;

public class App {
    public static void main(String[] args) {
        System.out.println(Greeter.hello("kim"));
    }
}

# greet.jar (Greeter.java compiled and packed into a jar)
package lib;

public class Greeter {
    public static String hello(String name) {
        return "hello, " + name;
    }
}

# Terminal
javac -cp greet.jar App.java
java -cp greet.jar;. App

Assumptions of this model

The compiler and runtime are JDK 21.0.12.1 (javac 21, java 21). All 4 combinations were checked. First, the class lib.Greeter (with one method, hello) was packed into greet.jar with javac and the jar tool (no external jar was downloaded), and for each combination, greet.jar and App.java were placed in a new folder and the terminal commands javac and java written on the screen were run exactly as written. The code on the screen is the same text that the probe made and kept from the actual file contents and the commands actually run. What was checked is whether the compile succeeded, the number of errors and the diagnostic codes (compiler.err.doesnt.exist and compiler.err.cant.resolve.location were rendered as the two error descriptions on the screen) and the lines where the errors occurred, whether App.class was created, the exit code and output of the java command, and the kind of the run error (NoClassDefFoundError) and its location (App.java:6). In the combinations where the compile failed, the java command was also actually run, and I confirmed that the error that the class App cannot be found occurs. The facts that the step sentences rely on were also checked by separate runs. These are that greet.jar contains lib/Greeter.class (jar tf), that the only class file created in the folder where the compile succeeded is App.class, that if you write only -cp greet.jar in the java command and leave out the dot (.), it cannot find App.class in the current folder, and that before the compile the folder had only App.java and greet.jar. The commands were run on Windows 11, and the semicolon (;) as the class path separator is for Windows (a colon on macOS and Linux). The screen shows only that one form. The CLASSPATH environment variable was cleared in the process that ran them, and in an environment where this variable is set, the result of a command without -cp can differ. The error sentence of the java launcher is shown in English; it was obtained by running the java command again with -Duser.language=en. On the PC used to write this lab (Korean Windows), without that option the sentence came out in Korean, and with other language settings the sentence is different. I opened the documentation and checked it. These are, in the javac tool documentation from Oracle (https://docs.oracle.com/en/java/javase/21/docs/specs/man/javac.html), that --class-path (-cp) sets where to look for user class files, that the current folder is used if neither -cp nor the CLASSPATH environment variable is set, and that the separator is a semicolon on Windows; in the java tool documentation (https://docs.oracle.com/en/java/javase/21/docs/specs/man/java.html; I read only the first 100,000 characters, and the explanation of -cp was within them), that -cp is a list of directories, JAR files, and ZIP files in which to look for class files, and that if it is not specified, it is the current folder (.); in the NoClassDefFoundError API documentation (https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/NoClassDefFoundError.html), that it is thrown when a class definition is to be loaded and cannot be found, and that it is the case where a definition that existed at compile time can no longer be found; in the Gradle documentation Declaring dependencies (https://docs.gradle.org/current/userguide/declaring_dependencies_basics.html), that a module dependency is written as group:name:version coordinates, with an example of writing it with implementation inside dependencies; and in the Java Plugin documentation (https://docs.gradle.org/current/userguide/java_plugin.html), in the table of configurations, that implementation is a dependency needed both at compile time and at run time and is included in compileClasspath and runtimeClasspath. Gradle was not run, and the process of downloading jars from a repository could not be checked in the documentation either, so it is not described. The number and kinds of errors and the error sentences are not set by a specification but were observed on this JDK. Beyond that, a library pulling in other libraries (transitive dependencies), versions, the order when the same class is in several places on the class path, and the module system are not covered. Dependencies and the supply chain are covered in the in-depth lab "Dependencies and the supply chain", and Spring Boot, which looks at the dependencies and creates the configuration automatically, is covered in the in-depth lab "Spring Boot auto-configuration".

Previous lab: File I/O · Next lab: Beans

Concepts in this lab

What this lab shows

Settings

Code


      
    

Diagram

Run comparison

Try these