Skip to content

Java 8 incompatible references of java.nio.Buffer classes generated by Java 9 compilers

Tomo Suzuki edited this page Sep 1, 2020 · 4 revisions

The Java 9 compiler and later can generate Java 8-incompatible bytecode through the --release 8 option. However, library developers sometimes fail to use this flag when they release the library. As the result, the generated bytecode contains references to methods that do not exist in Java 8 runtime. The invalid references manifest as NoSuchMethodError. For example, protobuf-java 3.12.4 had this problem and bigtable-client-core, which depends on protobuf-java, suffered the error:

https://github.com/protocolbuffers/protobuf/issues/7827

java.lang.NoSuchMethodError: java.nio.CharBuffer.flip()Ljava/nio/CharBuffer;
	at com.google.protobuf.TextFormat$Parser.toStringBuilder(TextFormat.java)

Why NoSuchMethodError?

In the example above, why does TextFormat$Parser throws NoSuchMethodError? In Java 8, java.nio.CharBuffer's flip() method has return type java.nio.Buffer. The class file of TextFormat$Parser tried to call the method but with return type java.nio.CharBuffer. The Java 8 runtime looks up method using both method name and types (both the parameter and return type). However, there's no method flip in Java 8's java.nio.CharBuffer that returns java.nio.CharBuffer.

Difficulty in Detection

Detecting this problem is difficult. Protobuf-java 3.12.4 was released on July 28th and the issue above was filed August 18th. By that time, around 172 Google Cloud Java client libraries had the version of protobuf in their dependencies.

Here are reasons why it's difficult to prevent:

  • The project might already set -target 8 in javac or <target>8</target> in Maven. This works for Java 8 but not enough for Java 9+.
  • The Java8 runtime can load the bytecode (major version: 52) without problem until it hits NoSuchMethodError.
  • The affected classes are small. It's java.nio.CharBuffer, java.nio.ByteBuffer, and java.nio.Buffer.
  • The problem does not surface when a pull request check uses only one Java version. Compiling the project in Java 11 and running tests in Java 8 would detect this issue but such setup is rare.

Solutions

Specify --release 8 flag

Explicitly Specify to use JDK 8

The problem occurs when we use JDK 9+ that targets Java 8 runtime. If we keep using JDK 8 for now, the generated bytecode is correct.

Cast to java.nio.Buffer

If we cast instances of CharBuffer and ByteBuffer to Buffer, then the generated bytecode will not have the bad references.

((Buffer)charBuffer).flip();

Tools

Animal Sniffer

Animal Sniffer can check your classes against the signature of different versions of Java runtime.

https://www.mojohaus.org/animal-sniffer/animal-sniffer-maven-plugin/examples/checking-signatures.html