Let's say I'm using Java 11 javac
, but I'm using the --source
and --target
options set to 1.8
so that my source code will be considered Java 8 and the output .class
files will be compatible with Java 8. My goal is to produce .class
files that can run on a Java 8 JVM.
And let's say I have the following Java 8 code I'm compiling:
import java.nio.ByteBuffer;
…
ByteBuffer byteBuffer = …; //init somehow
byteBuffer.flip(); //what ends up in the `.class` file?
The question is: what should Java 11 javac
put in the .class
file to link the byteBuffer.flip()
method call? Before you answer, consider this:
ByteBuffer
nor Java 11 ByteBuffer
declare the flip()
method at the ByteBuffer
level.ByteBuffer
is a direct subclass of Buffer
. There is both a Java 8 Buffer.flip()
and a Java 11 Buffer.flip()
declared in the Buffer
API for both versions.ByteBuffer.flip()
method.ByteBuffer
overrides the Buffer.flip()
method like the following. The purpose apparently was to use covariance so that in Java 11 ByteBuffer.flip()
would conveniently return a ByteBuffer
instead of a Buffer
.
@Override
public ByteBuffer flip() {
super.flip();
return this;
}
So to restate the question: Should Java 11 javac
, with the --source
and --target
options set to 1.8
, generate a .class
file that links to Buffer.flip()
or to ByteBuffer.flip()
? If it is the former, then how does it know not to include ByteBuffer.flip()
instead, as the (Java 8) code clearly references ByteBuffer.flip()
and the (Java 11) compiler sees that there is a ByteBuffer.flip()
method in the runtime? But if it is the latter, then how can I ever know that my 100% correct Java 8 compatible source code, when compiled using Java 11, will run on a Java 8 JRE even if I use the --source
and --target
options to indicate Java 8? (Note that OpenJDK 11.0.5 seems to choose the latter option. But which is correct?)
(Note that I'm using the word "link" loosely; I'm not currently well-versed in what bytecode is generated. All I know is that the class file has come reference somehow to Buffer.flip()
or ByteBuffer.flip()
; and if this method can't be found at runtime, the JVM will throw an exception such as: java.lang.NoSuchMethodError: java.nio.ByteBuffer.flip()Ljava/nio/ByteBuffer;
.)
As a bonus question, I wonder whether using the --release
option set for Java 8 would change the answer. But note that I can't use the --release
option (equivalent to the Maven <release>
compiler plugin option) because I want my Maven project to be buildable with both Java 8 and Java 11.
The class files created by Java 8 are still executable in Java 11; however, there have been other changes in the Java runtime (library changes, etc.) that might require modification of the code. These modifications may be made in Java 8 and compiled with Java 8 making it compatible with the Java 11 runtime.
Java 11 is backwards compatible with Java 8. So you can swiftly change from Java 8 to 11.
The most significant changes made to Java version 11 are as follows: As a preview, ZGC or Z Garbage Collector, a new, experimental garbage collector was added. The new toArray method was added to the Collections interface in Java 11.
If we take the following code and compile with Java 8 and with Java 11, we get the following bytecode, as seen when running javap -c MyClass.class
.
Java Source code
ByteBuffer byteBuffer = ByteBuffer.allocate(64);
byteBuffer.flip();
Java 8 bytecode
0: bipush 64
2: invokestatic #19 // Method java/nio/ByteBuffer.allocate:(I)Ljava/nio/ByteBuffer;
5: astore_1
6: aload_1
7: invokevirtual #25 // Method java/nio/ByteBuffer.flip:()Ljava/nio/Buffer;
10: pop
Java 11 bytecode
0: bipush 64
2: invokestatic #19 // Method java/nio/ByteBuffer.allocate:(I)Ljava/nio/ByteBuffer;
5: astore_1
6: aload_1
7: invokevirtual #25 // Method java/nio/ByteBuffer.flip:()Ljava/nio/ByteBuffer;
10: pop
As you can see, both of them "link" to the flip()
method of ByteBuffer
, even though the method isn't declared there for Java 8.
However, at the bytecode level, method signatures include the return type. This means that the JVM supports languages where you can overload methods that differ only in return type, even though Java doesn't support that.
The Java 11 version of the method has a different return type, which can be seen in the "linked" method, following the ()
, where Java 8 shows return type as Ljava/nio/Buffer;
and Java 11 shows return type as Ljava/nio/ByteBuffer;
.
When you take the code that was compiled against the Java 11 Runtime Library, and you try running it on Java 8, you get Exception in thread "main" java.lang.NoSuchMethodError: java.nio.ByteBuffer.flip()Ljava/nio/ByteBuffer;
This is why you should always specify the bootstrap class path to point to a Java Runtime Library matching the target Java version. When you compile using Java 11's javac
with options -source 8 -target 8
, it will actually warn you about that:
warning: [options] bootstrap class path not set in conjunction with -source 8
1 warning
This is why they implemented the newer --release <release>
option to replace -source
and -target
. If you compile with --release 8
, the generated .class
file will run without error on Java 8.
UPDATE
You don't need Java 8 installed to use option --release 8
. The Java 11 installation knows what the methods of the Java 8 Runtime Library were.
The --release
option was implemented in Java 9 as a result of JEP 247: Compile for Older Platform Versions, which says:
For JDK N and
--release
M, M < N, signature data of the documented APIs of release M of the platform is needed. This data is stored in the$JDK_ROOT/lib/ct.sym
file, which is similar, but not the same, as the file of the same name in JDK 8. Thect.sym
file is a ZIP file containing stripped-down class files corresponding to class files from the target platform versions.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With