Skip to content

Linkage Checker Enforcer Rule Tutorial

Tomo Suzuki edited this page Sep 6, 2019 · 23 revisions

In this tutorial, you’ll learn how to apply the enforcer rule to statically detect Java diamond dependency conflicts using the enforcer rule. The target audience is Java developers who already know Maven command (mvn) and JDK 8.

Setup

Checkout project via Git. Our project contains example-problems directory.

$ git clone https://github.com/GoogleCloudPlatform/cloud-opensource-java.git
$ cd cloud-opensource-java/example-problems/no-such-method-error-signature-mismatch

The following commands assume that this no-such-method-error-signature-mismatch as current working directory.

Compile and run the project. You observe NoSuchMethodError:

$ mvn clean compile exec:java
...
java.lang.NoSuchMethodError: com.google.common.base.Verify.verify(ZLjava/lang/String;Ljava/lang/Object;)V
    at io.grpc.internal.DnsNameResolver.maybeChooseServiceConfig (DnsNameResolver.java:514)
    at io.grpc.internal.App.main (App.java:31)
...
[INFO] BUILD FAILURE

Now you have a Maven project with a diamond dependency conflict.

Diagnosis of Linkage Error

(This section can be skipped if you just want to learn the enforcer rule.)

Why did the NoSuchMethodError occur? Let’s look at dependency graph. Maven has built-in 'dependency' plugin to show the graph.

$ mvn dependency:tree -Dverbose=true
[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ no-such-method-error-example ---
[INFO] com.google.cloud.tools.opensource:no-such-method-error-example:jar:1.0-SNAPSHOT
[INFO] +- com.google.api-client:google-api-client:jar:1.27.0:compile
[INFO] |  +- com.google.oauth-client:google-oauth-client:jar:1.27.0:compile
[INFO] |  |  +- com.google.http-client:google-http-client:jar:1.27.0:compile
[INFO] |  |  |  +- (com.google.code.findbugs:jsr305:jar:3.0.2:compile - omitted for duplicate)
[INFO] |  |  |  +- (com.google.guava:guava:jar:20.0:compile - omitted for duplicate)
[INFO] |  |  |  +- org.apache.httpcomponents:httpclient:jar:4.5.5:compile
[INFO] |  |  |  |  +- org.apache.httpcomponents:httpcore:jar:4.4.9:compile
[INFO] |  |  |  |  +- commons-logging:commons-logging:jar:1.2:compile
[INFO] |  |  |  |  \- commons-codec:commons-codec:jar:1.10:compile
[INFO] |  |  |  \- com.google.j2objc:j2objc-annotations:jar:1.1:compile
[INFO] |  |  +- (com.google.code.findbugs:jsr305:jar:3.0.2:compile - omitted for duplicate)
[INFO] |  |  \- (com.google.guava:guava:jar:20.0:compile - omitted for duplicate)
[INFO] |  +- com.google.http-client:google-http-client-jackson2:jar:1.27.0:compile
[INFO] |  |  +- (com.google.http-client:google-http-client:jar:1.27.0:compile - omitted for duplicate)
[INFO] |  |  \- com.fasterxml.jackson.core:jackson-core:jar:2.9.6:compile
[INFO] |  \- com.google.guava:guava:jar:20.0:compile
[INFO] \- io.grpc:grpc-core:jar:1.17.1:compile
[INFO]    +- io.grpc:grpc-context:jar:1.17.1:compile
[INFO]    +- com.google.code.gson:gson:jar:2.7:compile
[INFO]    +- com.google.errorprone:error_prone_annotations:jar:2.2.0:compile
[INFO]    +- com.google.code.findbugs:jsr305:jar:3.0.2:compile
[INFO]    +- org.codehaus.mojo:animal-sniffer-annotations:jar:1.17:compile
[INFO]    +- (com.google.guava:guava:jar:26.0-android:compile - omitted for conflict with 20.0)
[INFO]    +- io.opencensus:opencensus-api:jar:0.17.0:compile
[INFO]    \- io.opencensus:opencensus-contrib-grpc-metrics:jar:0.17.0:compile
[INFO]       \- (io.opencensus:opencensus-api:jar:0.17.0:compile - omitted for duplicate)

The output shows it selected com.google.guava:guava:jar:20.0:compile, which is a dependency of com.google.api-client:google-api-client:jar:1.27.0:compile. It also shows (com.google.guava:guava:jar:26.0-android:compile - omitted for conflict with 20.0) as a dependency of io.grpc:grpc-core:jar:1.17.1:compile. The message "omitted for conflict with 20.0" means Maven did not picked up this version "26.0-android" in favor of version "20.0".

Next, let's check the difference between guava version 20.0 and 26.0-android.

The first thing you notice is the suffix "-android". Recent Guava releases publish two flavor of builds: one is for JRE 8 (suffix "-jre") and the other is for JRE 7 and Android platform (suffix "-android") (Adding Guava to your build). The artifact com.google.api-client:google-api-client uses "-android" flavor because the artifact supports JRE 7.

What about the missing method com.google.common.base.Verify.verify(ZLjava/lang/String;Ljava/lang/Object;)V? The method signature is encoded in Java VM-internal format. The table in 4.2. The Internal Form of Names helps you decode the message.

com.google.common.base.Verify.verify(ZLjava/lang/String;Ljava/lang/Object;)V

Is verify method in class com.google.common.base.Verify that takes arguments (boolean, String, Object) with return value void.

Linkage Checker Enforcer Rule

Apply Linkage Checker Enforcer Rule to the project.

Add following section to the pom.xml.

Run the enforcer rule.

It detects the errors.

Fix the Linkage Error

The error occurred because the two artifacts and their transitive dependencies are not compatible with each other. To fix the problem, you change the version of the XXX:

Run the linkage checker enforcer rule again. This time you don’t see the errors: Run the main class again. This time it succeeds to run: