Preventing Java API/ABI breaks with gradle-revapi

Palantir
Palantir
Sep 25 · 3 min read

When you’re dealing with hundreds of Java microservices, API/ABI breaks from low-level libraries can be painful. We’ve starting using the open-source Revapi back-compat checker, and have written a Gradle plugin which we’ve just open-sourced. This blog post explains why we use Revapi and sketches how to use the Gradle plugin.

Palantir Foundry is made up of over 100 microservices using hundreds of both open source and internally developed libraries. To ensure isolation, we avoid cross-microservice dependencies (preferring to expose pure Conjure APIs), but some low-level libraries appear everywhere. For example, our open-source tracing and HTTP client libraries are used by pretty much every Java service. With any amount of code sharing there is risk: breaking change in the API or ABI of these widely used libraries can cause problems at compile time (or worse at runtime) for a large number of consumers.

For example, in tritium, our library for handling metrics in Java services, a seemingly innocuous PR changed a method return type as follows:

- Map<String, String> safeTags();
+ SortedMap<String, String> safeTags();

This compiled fine within the project and even with direct consumers of the library (since a SortedMap is a Map) but caused errors are runtimebecause our logging library expected the Map signature, which no longer existed in the published tritium jar, resulting in a dreaded NoSuchMethodError:

java.lang.NoSuchMethodError: com.palantir.tritium.metrics.registry.MetricName.safeTags()Ljava/util/Map;
at com.palantir.sls.logging.metric.ScheduledTaggedMetricLogReporter.metricLog(ScheduledTaggedMetricLogReporter.java:121)
at com.palantir.sls.logging.metric.ScheduledTaggedMetricLogReporter.reportMetrics(ScheduledTaggedMetricLogReporter.java:102)

This was what’s known as an Application Binary Interface (ABI) break, and caused a couple of production incidents, despite our best efforts at remediating. At the end of the day, we didn’t spot the danger of this change as part of a normal code review. Covariant return type changes are only one of many subtle ways an API or ABI break can occur. By augmenting human code reviews with automated tooling like Revapi, we can fearlessly refactor and rely on tooling to warn developers when they’ve made a breaking change.

Revapi

Revapi is an open-source API checker for Java projects which can detect API and ABI breaks. Since we are heavy Gradle users and Revapi only supplies a Maven plugin, we created gradle-revapi, a zero config Gradle plugin that wires up the configuration and reporting of Revapi, checking against the previously published version of your jars. It also integrates nicely with CircleCI so that failures are rendered at the top of the page.

In fact, to revisit to tritium change described above, Revapi would haved warned us that this was a bad change (among the other expected breaks):

This would have alerted the PR authors that something bad happened and hopefully avoided all the library consumer pain and problems in production!

How do I use it?

The Revapi Gradle plugin can be added to a Gradle project in the standard way. First add the plugin dependency:

buildscript {
// ...
dependencies {
classpath 'com.palantir.gradle.revapi:gradle-revapi:<latest-version>'
}
}

Then apply it to each project that publishes an API:

// In my Java project's build.gradle that publishes a jar
apply plugin: 'com.palantir.revapi'

Check out the readme on GitHub for more details.


Authors

Callum R., Dan F., Dan S., Felipe O.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade