Squash Java Linkage Bugs Before They Bite

Elliotte Rusty Harold
Google Cloud - Community
3 min readMar 23, 2020

A typical modern Java project depends on many libraries, each of which depends on multiple libraries. Some dependencies appear multiple times in the full transitive dependency graph, others only once. Sometimes the versions of the repeated libraries are the same. Often they’re not, and, when they differ, programs can fail.

For example, org.apache.beam:beam-sdks-java-io-cassandra might call a method in Guava 20 while com.google.cloud:google-cloud-firestore calls a renamed version of that method in Guava 28. Declaring the dependencies on both of them introduces a hidden incompatibility because the class path can contain only one version of Guava. Even if com.google.cloud:google-cloud-firestore doesn’t call the renamed method at all, it can still unexpectedly break Cassandra by swapping in Guava 28 instead of 20 (the version Cassandra needs). This incompatibility between libraries is called a linkage error because one of the libraries is linked to an incompatible version of Guava at runtime. Problems like this usually manifest with runtime exceptions such as NoSuchMethodError, NoClassDefFoundError, IllegalAccessError, and so forth.

The Maven Enforcer plugin already offers several strategies to mitigate linkage errors including requiring upper bounds and dependency convergence. Upper bounds checks are useful for fixing cases where a method has been added in a newer version of a library, but fail when the problem is that a method has been removed. Dependency convergence is the gold standard for avoiding linkage errors but is difficult to achieve in complex projects composed of many libraries from many independent teams.

The Linkage Checker Enforcer Rule is a new rule the Maven Enforcer plugin can apply to projects. It is especially useful when full dependency convergence isn’t possible. This rule inspects the entire class path of a project and reports all linkage errors. For example, in the project below, the JettyNpnSslEngine class from gRPC is supposed to link to the NextProtoNego class from Jetty but the latter isn’t actually in the class path:

$ mvn verify
[INFO] - - maven-enforcer-plugin:3.0.0-M3:enforce (enforce-linkage-checker) @ google-cloud-core-grpc - -
[ERROR] Linkage Checker rule found 21 reachable errors. Linkage error report:
Class org.eclipse.jetty.npn.NextProtoNego is not found; referenced by 1 class file
io.grpc.netty.shaded.io.netty.handler.ssl.JettyNpnSslEngine (grpc-netty-shaded-1.23.0.jar)

This particular issue was caused by incomplete shading, a very common source of linkage errors.

Whether a linkage error causes problems at runtime depends on which code paths the program follows. These problems can lurk undetected for months and then appear unexpectedly in production. Your program might never invoke the method with a linkage error. On the other hand, if another project that depends on your project does invoke this method, they’ll get a nasty surprise. Problems like this are hard, expensive, and time consuming to debug. The Linkage Checker Enforcer Rule reveals these issues much earlier in the development cycle when they are easier and cheaper to fix.

To check linkage in a Maven project, add this plugin element to the pom.xml:

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.0.0-M3</version>
<dependencies>
<dependency>
<groupId>com.google.cloud.tools</groupId>
<artifactId>linkage-checker-enforcer-rules</artifactId>
<version>1.1.4</version>
</dependency>
</dependencies>
<executions>
<execution>
<id>enforce-linkage-checker</id>
<phase>verify</phase>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<LinkageCheckerRule implementation= "com.google.cloud.tools.dependencies.enforcer.LinkageCheckerRule">
<level>WARN</level>
</LinkageCheckerRule>
</rules>
</configuration>
</execution>
</executions>
</plugin>

Now mvn verify warns of all linkage errors in your project: missing classes, private methods called from another class, methods with an unexpected number of arguments, etc. If you prefer to completely fail the build when a linkage error is detected, set the level to ERROR instead.

More details about linkage checking can be found on the rule’s Wiki. You might also be interested in the best practices we’ve published for managing dependencies in Java libraries.

Linkage errors are a major source of pain in all but the simplest Java projects. The Linkage Checker Enforcer Rule provides a way to find these problems early and fix them before they require difficult debugging.

--

--

Elliotte Rusty Harold
Google Cloud - Community

When not laboring in his secret identity of a mild-mannered Google SWE, Elliotte Rusty Harold photographs dinosaurs and insects.