Symphonious

Living in a state of accord.

Fun with Java Backwards Compatibility

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.