-
Bug
-
Resolution: Unresolved
-
P4
-
9, 11, 17, 21, 24
The [`javac` specification](https://docs.oracle.com/en/java/javase/22/docs/specs/man/javac.html) for '-Xprefer:[source, newer]' reads as follows:
> '-Xprefer:newer': Reads the newer of the source or class files for a type (default).
This leaves the case unspecified, where the class and the source file have the same time stamp.
Up to and including JDK 8, `javac` chooses the `.class` file over the `.java` file if both are present and have the same time stamp. Starting with JDK 9 and later, `javac` chooses whatever comes last in the search path. If e.g. a `.jar` file on the classpath contains both, a `.class` file and a `.java` source file for the same class, then if the `.java` file comes first, `javac` from JDK 9+ behaves exactly like `javac` from JDK 8 and will not compile the `.java` file from the `.jar` file. Only if the `.java` file comes after the corresponding `.class` file, then the behavior of the JDK 9+ `javac` differs from that in JDK 8 in that it will choose to compile the `.java` file from the `.jar` file and place the resulting `.class` file in the output directory.
This can easily be reproduced with the following example code:
```
$ mkdir test && cd test;
$ echo 'public class A { static final String S = "A"; } ' > A.java
$ javac A.java
$ touch -t 03210321 A.java A.class
$ zip A_equal_timestamp_java_class.jar A.java A.class
$ zip A_equal_timestamp_class_java.jar A.class A.java
$ touch -t 03210320 A.java
$ zip A_class_newer_java_class.jar A.java A.class
$ zip A_class_newer_class_java.jar A.class A.java
$ touch -t 03210322 A.java
$ zip A_java_newer_java_class.jar A.java A.class
$ zip A_java_newer_class_java.jar A.class A.java
$ rm A.class
$ echo 'public class B { static final String S = A.S; } ' > B.java
$ mkdir build
```
With JDK 8:
```
$ rm -f build/* && javac -cp A_equal_timestamp_java_class.jar -d build/ B.java
$ ls build/
B.class
$ rm -f build/* && javac -cp A_equal_timestamp_class_java.jar -d build/ B.java
$ ls build/
B.class
$ rm -f build/* && javac -cp A_class_newer_java_class.jar -d build/ B.java
$ ls build/
B.class
$ rm -f build/* && javac -cp A_class_newer_class_java.jar -d build/ B.java
$ ls build/
B.class
$ rm -f build/* && javac -cp A_java_newer_java_class.jar -d build/ B.java
$ ls build/
A.class B.class
$ rm -f build/* && javac -cp A_java_newer_class_java.jar -d build/ B.java
ls build/
A.class B.class
```
For JDK 8, if the `.class` file is newer, it will be used and if the `.java` file is newer it will be used and compiled. If both `.class` and `.java` file have the same time stamp, JDK 8 chooses the `.class` file.
With JDK 9+:
```
$ rm -f build/* && javac -cp A_equal_timestamp_java_class.jar -d build/ B.java
$ ls build/
B.class
$ rm -f build/* && javac -cp A_equal_timestamp_class_java.jar -d build/ B.java
$ ls build/
A.class B.class
$ rm -f build/* && javac -cp A_class_newer_java_class.jar -d build/ B.java
$ ls build/
B.class
$ rm -f build/* && javac -cp A_class_newer_class_java.jar -d build/ B.java
$ ls build/
B.class
$ rm -f build/* && javac -cp A_java_newer_java_class.jar -d build/ B.java
$ ls build/
A.class B.class
$ rm -f build/* && javac -cp A_java_newer_class_java.jar -d build/ B.java
$ ls build/
A.class B.class
```
For JDK 9+, the cases where either one of the `.class` or `.java` file is newer are handled fine (and in the same way like in JDK 8). But if both `.class` and `.java` file have the same time stamp than it depends on their relative positon in the `.jar` file whether the `.class` file will be chosen or the `.java` file will be chosen and compiled into the output directory.
> '-Xprefer:newer': Reads the newer of the source or class files for a type (default).
This leaves the case unspecified, where the class and the source file have the same time stamp.
Up to and including JDK 8, `javac` chooses the `.class` file over the `.java` file if both are present and have the same time stamp. Starting with JDK 9 and later, `javac` chooses whatever comes last in the search path. If e.g. a `.jar` file on the classpath contains both, a `.class` file and a `.java` source file for the same class, then if the `.java` file comes first, `javac` from JDK 9+ behaves exactly like `javac` from JDK 8 and will not compile the `.java` file from the `.jar` file. Only if the `.java` file comes after the corresponding `.class` file, then the behavior of the JDK 9+ `javac` differs from that in JDK 8 in that it will choose to compile the `.java` file from the `.jar` file and place the resulting `.class` file in the output directory.
This can easily be reproduced with the following example code:
```
$ mkdir test && cd test;
$ echo 'public class A { static final String S = "A"; } ' > A.java
$ javac A.java
$ touch -t 03210321 A.java A.class
$ zip A_equal_timestamp_java_class.jar A.java A.class
$ zip A_equal_timestamp_class_java.jar A.class A.java
$ touch -t 03210320 A.java
$ zip A_class_newer_java_class.jar A.java A.class
$ zip A_class_newer_class_java.jar A.class A.java
$ touch -t 03210322 A.java
$ zip A_java_newer_java_class.jar A.java A.class
$ zip A_java_newer_class_java.jar A.class A.java
$ rm A.class
$ echo 'public class B { static final String S = A.S; } ' > B.java
$ mkdir build
```
With JDK 8:
```
$ rm -f build/* && javac -cp A_equal_timestamp_java_class.jar -d build/ B.java
$ ls build/
B.class
$ rm -f build/* && javac -cp A_equal_timestamp_class_java.jar -d build/ B.java
$ ls build/
B.class
$ rm -f build/* && javac -cp A_class_newer_java_class.jar -d build/ B.java
$ ls build/
B.class
$ rm -f build/* && javac -cp A_class_newer_class_java.jar -d build/ B.java
$ ls build/
B.class
$ rm -f build/* && javac -cp A_java_newer_java_class.jar -d build/ B.java
$ ls build/
A.class B.class
$ rm -f build/* && javac -cp A_java_newer_class_java.jar -d build/ B.java
ls build/
A.class B.class
```
For JDK 8, if the `.class` file is newer, it will be used and if the `.java` file is newer it will be used and compiled. If both `.class` and `.java` file have the same time stamp, JDK 8 chooses the `.class` file.
With JDK 9+:
```
$ rm -f build/* && javac -cp A_equal_timestamp_java_class.jar -d build/ B.java
$ ls build/
B.class
$ rm -f build/* && javac -cp A_equal_timestamp_class_java.jar -d build/ B.java
$ ls build/
A.class B.class
$ rm -f build/* && javac -cp A_class_newer_java_class.jar -d build/ B.java
$ ls build/
B.class
$ rm -f build/* && javac -cp A_class_newer_class_java.jar -d build/ B.java
$ ls build/
B.class
$ rm -f build/* && javac -cp A_java_newer_java_class.jar -d build/ B.java
$ ls build/
A.class B.class
$ rm -f build/* && javac -cp A_java_newer_class_java.jar -d build/ B.java
$ ls build/
A.class B.class
```
For JDK 9+, the cases where either one of the `.class` or `.java` file is newer are handled fine (and in the same way like in JDK 8). But if both `.class` and `.java` file have the same time stamp than it depends on their relative positon in the `.jar` file whether the `.class` file will be chosen or the `.java` file will be chosen and compiled into the output directory.
- relates to
-
JDK-8338675 javac shouldn't silently change .jar files on the classpath
-
- Open
-
-
JDK-8338685 javac: issue a warning if sources in a .jar file on the classpath override classes in that .jar file
-
- Open
-