Fun with Java Backwards Compatibility
By Adrian Sutton
There’s a fun little gotcha introduced in Java 10 which causes trouble for anyone wanting to support earlier JVMs. If you take the code:
import java.nio.ByteBuffer;
public class ProblemCode {
public static void main(String[] args) {
final ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.position(10);
System.out.println("Yay it worked!");
}
}
If you compile it on Java 8 it will run on any Java from 8 and above. If you compile it on Java 10 or above it will only work on Java 10 and above, even if you specify -target 8 -source 8
.
The problem is that you’re compiling against the Java 10 libraries and Java 10 changed the ByteBuffer.position return type. Previously it was inherited from Buffer and so returned a Buffer but Java 10 took advantage of co-variant return types and now returns ByteBuffer so it’s easier to use in a fluent style. Compiled against Java 10 libraries the compiler embeds a reference to the Java 10 version which results in a NoSuchMethodError on Java 8:
Exception in thread "main" java.lang.NoSuchMethodError: java.nio.ByteBuffer.position(I)Ljava/nio/ByteBuffer;
at ProblemCode.main(ProblemCode.java:6)
Javac helpfully provides a warning which suggest how to solve the issue:
warning: [options] bootstrap class path not set in conjunction with -source 8
which has been best practice for many, many years anyway but it’s extremely common to skip that step because going and finding the libraries from Java 8 is a challenge and they believe they can make things work just by avoiding new APIs. Often that’s tested by periodically checking the code compiles on Java 8 as well.
This case shows that’s not enough. It’s possible to have code that compiles correctly on Java 8 and 10 but is not backwards compatible when compiled against Java 10 libraries.