adrien v adrien v - 9 months ago 66
Java Question

javac classpath order contradicts Oracle documentation?

In Sierra/Bates SCJP book page 797:

"java and javac [...] first look in the directories that contain the classes that come standard with Java SE.
Then they look in the directories defined by classpaths"

Oracle documentation is stating the same order.

(I know I shouldn't do that but...) To test this behaviour I implemented and in directory:

package java.util;
public class HashSet {}


package java.util;
import java.util.HashSet;
public class Lol {
public static void main(String... x) {
HashSet a = new HashSet();

I get a compilation error when executing:
C:\dev\cmdline\TestProject\sources>javac java/util/

java\util\ error: cannot find symbol
symbol: method add(String)
location: variable a of type HashSet

...which means that the default classpath (current directory) is first used.

So, is the Oracle documentation wrong? How would you test the classpaths order?

Answer Source

Referring to the Oracle Documentation, the statement from the SCJP book may be oversimplified. The Oracle Documentation explicitly differentiates between the "Java Launcher" (java) and the Java Compiler javac. And in fact, the processes are somewhat different.

I'll try to extract the relevant parts that explain the behavior that you are observing:

(From How Classes are Found : How Javac and JavaDoc Find Classes:)

If a referenced class is defined in both a class file and source file, [...] javac uses class files, but automatically recompiles any class files it determines to be out of date. The rules for automatic recompilation are documented in the javac document for Windows or Solaris.

These linked docments contain the corresponding subsection (which is the same in both cases), from which I'll quote here again:

(From javac - Java programming language compiler : SEARCHING FOR TYPES:)

When compiling a source file, the compiler often needs information about a type whose definition did not appear in the source files given on the command line. [...]

When the compiler needs type information, it looks for a source file or class file which defines the type. [...]

A successful type search may produce a class file, a source file, or both. If both are found, you can use the -Xprefer option to instruct the compiler which to use. If newer is given, the compiler will use the newer of the two files. If source is given, it will use the source file. The default is newer.

If a type search finds a source file for a required type, either by itself, or as a result of the setting for -Xprefer, the compiler will read the source file to get the information it needs. In addition, it will by default compile the source file as well. You can use the -implicit option to specify the behavior. If none is given, no class files will be generated for the source file. If class is given, class files will be generated for the source file.

So to summarize: The javac compiler will find your source file for java.util.HashSet, as well as the class file from the bootstrap classes. But by default, it will compile the source file.

(And interestingly, there seems to be no easy way to convince him not to use the source as input: The -implicit option only determines whether a .class file is generated, but even if -implicit:none is set, it will still use the class that was created from the source...)

You can also use the -verbose option to watch this process in more detail:

javac -verbose java/util/

produces the following output:

[parsing started RegularFileObject[java\util\]]
[parsing completed 100ms]
[search path for source files: .]
[search path for class files: (A long list with rt.jar and related JARs)]
[loading RegularFileObject[.\java\util\]]
[parsing started RegularFileObject[.\java\util\]]
[parsing completed 0ms]
[loading ZipFileIndexFileObject[c:\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Object.class)]]
[loading ZipFileIndexFileObject[c:\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/String.class)]]
[checking java.util.Lol]
[loading ZipFileIndexFileObject[c:\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/AutoCloseable.class)]]
[loading ZipFileIndexFileObject[c:\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Byte.class)]]
[loading ZipFileIndexFileObject[c:\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Character.class)]]
[loading ZipFileIndexFileObject[c:\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Short.class)]]
[loading ZipFileIndexFileObject[c:\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Long.class)]]
[loading ZipFileIndexFileObject[c:\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Float.class)]]
[loading ZipFileIndexFileObject[c:\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Integer.class)]]
[loading ZipFileIndexFileObject[c:\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Double.class)]]
[loading ZipFileIndexFileObject[c:\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Boolean.class)]]
[loading ZipFileIndexFileObject[c:\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Void.class)]]
java\util\ error: cannot find symbol
  symbol:   method add(String)
  location: variable a of type HashSet
[checking java.util.HashSet]
[total 1072ms]
1 error

It does not even try to load the HashSet` class from the bootstrap JARs, but instead, directly refers to your source file:

[loading RegularFileObject[.\java\util\]]

In contrast, when you omit your own HashSet class, you'll see the expected output:

[parsing started RegularFileObject[java\util\]]
[parsing completed 100ms]
[search path for source files: .]
[search path for class files: (A long list with rt.jar and related JARs) ]
[loading ZipFileIndexFileObject[c:\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/util/HashSet.class)]]
[loading ZipFileIndexFileObject[c:\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Object.class)]]

where it obtains the HashSet class from the rt.jar.