I've known almost since I started learning about Java that the Class-Path header field in a Manifest file specifies the relative runtime classpath for executable JARs (JARs with application starting point specified by another manifest header called Main-Class
). A colleague recently ran into an issue that surprised me because it proved that a JAR file's Manifest's Class-Path
entry also influences the compile-time classpath when the containing JAR is included on the classpath while running javac. This post demonstrates this new-to-me nuance.
The section "Adding Classes to the JAR File's Classpath" of the Deployment Trail of The Java Tutorials states, "You specify classes to include in the Class-Path
header field in the manifest file of an applet or application." This same section also states, "By using the Class-Path
header in the manifest, you can avoid having to specify a long -classpath
flag when invoking Java to run the your application." These two sentences essentially summarize how I've always thought of the Class-Path
header in a manifest file: as the classpath for the containing JAR being executed via the Java application launcher (java executable).
It turns out that the Class-Path
entry in a JAR's manifest affects the Java compiler (javac) just as it impacts the Java application launcher (java). To demonstrate this, I'm going to use a simple interface (PersonIF
), a simple class (Person
) that implements that interface, and a simple class Main
that uses the class that implements the interface. The code listings are shown next for these.
public interface PersonIF { void sayHello(); }Person.java
import static java.lang.System.out; public class Person implements PersonIF { public void sayHello() { out.println("Hello!"); } }Main.java
public class Main { public static void main(final String[] arguments) { final Person person = new Person(); person.sayHello(); } }
As can be seen from the code listings above, class Main
depends upon (uses) class Person
and class Person
depends upon (implements) PersonIF
. I will intentionally place the PersonIF.class
file in its own JAR called PersonIF.jar
and will store that JAR in a (different) subdirectory. The Person.class
file will exist in its own Person.jar
JAR file and that JAR file includes a MANIFEST.MF file
with a Class-Path
header referencing PersonIF.jar
in the relative subdirectory.
I will now attempt to compile the Main.class
from Main.java
with only the current directory on the classpath. I formerly would have expected compilation to fail when javac
would be unable to find PersonIF.jar
in a separate subdirectory. However, it doesn't fail!
This seemed surprising to me. Why did this compile when I had not explicitly specified PersonIF.class
(or a JAR containing it) as the value of classpath provided via the -cp
flag? The answer can be seen by running javac
with the -verbose
flag.
The output of javac -verbose
provides the "search path for source files" and the "search path for class files". The "search path for class files" was the significant one in this case because I had moved the PersonIF.java
and Person.java
source files to a completely unrelated directory not in those specified search paths. It's interesting to see that the search path for class files (as well as the search path for source files) includes archive/PersonIF.jar
even though I did not specify this JAR (or even its directory) in the value of -cp
. This demonstrates that the Oracle-provided Java compiler considers the classpath content specified in the Class-Path
header of the MANIFEST.MF
of any JAR on specified on the classpath.
The next screen snapshot demonstrates running the newly compiled Main.class
class and having the dependency PersonIF.class
picked up from archive/PersonIF.jar
without it being specified in the value passed to the Java application launcher's java -cp
flag. I expected the runtime behavior to be this way, though admittedly I had never tried it or even thought about doing it with a JAR whose MANIFEST.MF
file did not have a Main-Class
header (non-executable JAR). The Person.jar
manifest file in this example did not specify a Main-Class
header and only specified a Class-Path
header, but was still able to use this classpath content at runtime when invoked with java
.
The final demonstration for this post involves removing the Class-Path
header and associated value from the JAR file and trying to compile with javac
and the same command-line-specified classpath. In this case, the JAR containing Person.class
is called Person2.jar
and the following screen snapshot demonstrates that its MANIFEST.MF
file does not have a Class-Path
header.
The next screen snapshot demonstrates that compilation with javac
fails now because, as expected, PersonIF.class
is not explicitly specified on the classpath and is no longer made available by reference from the MANIFEST.MF
Class-Path
header of a JAR that is on the classpath.
We see from the previous screen snapshot that the search paths for source files and for class files no longer include archive/PersonIF.jar
. Without that JAR available, javac
is unable to find PersonIF.class
and reports the error message: "class file for PersonIF not found."
General Observations
- The
Class-Path
header in aMANIFEST.MF
file has no dependency on the existence of aMain-Class
header existing in the same JAR'sMANIFEST.MF
file.- A JAR with a
Class-Path
manifest header will make those classpath entries available to the Java classloader regardless of whether that JAR is executed withjava -jar ...
or is simply placed on the classpath of a larger Java application. - A JAR with a
Class-Path
manifest header will make those classpath entries available to the Java compiler (javac
) if that JAR is included in the classpath specified for the Java compiler.
- A JAR with a
- Because the use of
Class-Path
in a JAR's manifest file is not limited in scope to JARs whoseMain-Class
is being executed, class dependencies can be potentially inadvertently satisfied (perhaps even with incorrect versions) by these rather than resolving explicitly specified classpath entries. Caution is advised when constructing JARs with manifests that specifyClass-Path
or when using third-party JARs withClass-Path
specified in their manifest files. - The importance of the JAR's manifest file is sometimes understated, but this topic is a reminder of the usefulness of being aware of what's in a particular JAR's manifest file.
- This topic is a reminder of the insight that can be gleaned from running
javac
now and then with the-verbose
flag to see what it's up to. - Whenever you place a JAR on the classpath of the
javac
compiler or thejava
application launcher, you are placing more than just the class definitions within that JAR on the classpath; you're also placing any classes and JARs referenced by that JAR's manifest'sClass-Path
on the classpath of the compiler or application launcher.
Conclusion
There are many places from which a Java classloader may load classes for building and running Java applications. As this post has demonstrated, the Class-Path
header of a JAR's MANIFEST.MF
file is another touch point for influencing which classes the classloader will load both at runtime and at compile time. The use of Class-Path
does not affect only JARs that are "executable" (have a Main-Class
header specified in their manifest file and run with java -jar ...
), but can influence the loaded classes for compilation and for any Java application execution in which the JAR with the Class-Path
header-containing manifest file lies on the classpath.
1 comment:
And of course it affects EAR classloading as well.
Post a Comment