Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How should javac 11 link methods overridden in later versions with a Java 8 target?

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:

  • Neither Java 8 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.
  • In the Java 8 source code, there is no ByteBuffer.flip() method.
  • But in the Java 11 source code, 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.

like image 566
Garret Wilson Avatar asked Nov 17 '20 03:11

Garret Wilson


People also ask

Can Java 11 compiled code run on Java 8?

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.

Is Java 11 fully backward compatible with Java 8?

Java 11 is backwards compatible with Java 8. So you can swiftly change from Java 8 to 11.

What changed from Java 8 to Java 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.


1 Answers

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. The ct.sym file is a ZIP file containing stripped-down class files corresponding to class files from the target platform versions.

like image 150
Andreas Avatar answered Oct 19 '22 13:10

Andreas