Jackson Guava compatibility

(which Guava versions are supported by “jackson-datatype-guava”)

Jackson support of Guava datatypes: basics

Jackson core components like databind do not use Guava for anything: not because it was not a very useful library (it is!), but to keep Jackson core components dependency-free (*).
But there is an extension module available to let Jackson users use databind to read and write many Guava values: jackson-datatype-guava extension modules.
It is registered like so:

and after doing this you can read and write many of standard Guava datatypes.

For example:

Use of this module is optional so that applications that do want to serialize/deserialize Guava types specifically (note: you can serialize (write) Guava collection types just fine without this module, but not deserialize (read)) can do so, but other applications are not given forced Guava dependnecy.

(*) aka “Zero Dependency”: the reason being that this avoid a whole class of transitive dependency/versioning issues, see for example this blog post.

Guava support & versioning challenged

So far so good. But one big challenge with Guava usage, in general, is that the library has changed a lot over the years. Due to Guava’s aggressive deprecation and removal of old functionality, it is not easy to support multiple Guava versions. Code written for version 14.0, for example, may not compile or run against version 18.0, due to API changes like removal of methods or classes.

When using Guava types in your own code this is not necessarily very problematic: when upgrading to a new Guava version you can update your code as necessary. Your code needs to work with just one Guava version.

But this is not the case for Jackson Guava module: it cannot reasonably only support one specific Guava “major” version — some Jackson users use older, some newer versions of Guava, and any given major version covers but a small slice of the user base. Older versions of Guava are often used by applications due to transitive dependencies: some library, written a while while, may depend on an old Guava version, and break if upgrade was attempted (since newer Guava versions have made backwards-incompatible changes). These kinds of dependencies cause cascading issues throughout Java dependency chains.

Jackson Guava module is between a rock and a hard place trying to balance this versioning challenge.

Given this, Jackson project had two main choices regarding Guava compatibility:

  1. Create version matrix so that Guava module would have NxM versions like “jackson-datatype-guava:2.12.4_21” (where Jackson version is 2.12.4, supported Guava version 21.x) — leading to a growing number of pseudo-versions, similar to fragmentation of Scala libraries
  2. Support a set of “core” Guava datatypes and attempt to keep compatibility across a wide set of Guava versions for those types.

Of these, former would be A LOT of work and would probably be relatively brittle as well. It might be possible to do that with a dedicated templating system (to support variation, optional inclusion of handlers for old/new Guava versions), but even the sheer number of versions to publish would be daunting: number of version combinations would grow over time and probably keep effective range of actually supported Guava versions low.

Instead, option (2) turned out to work surprisingly well, at least so far: while Guava API has changed a lot with respect to methods and functionality, the core set of data types that are commonly serialized has not changed that drastically. While there are couple of types for which bit of custom logic is needed — HostAndPort comes to mind — need for workarounds has been quite low.
In practice Jackson Guava module:

  • Uses one specific version as the “default” (or preferred) Guava version: this is the dependency version declared in pom.xml and this is the version against which compilation and unit tests run
  • Works across wider range of Guava versions so that users need not use the same Guava version

But what Guava versions are actually supported?

Knowing that Jackson Guava modules with a “wider set of versions than just one” is not very useful without knowing exactly which versions are supported.
But until fairly recently users would have had to just “try it out” to know if a given version is supported. While it always seemed that most Guava versions anyone uses do work (based on lack of reports to the contrary) there was no clear definition or testing to verify this assumption.

So I decided to dig into the compatibility aspects a while ago (some time in 2020).

Testing setup itself is pretty naïve: I used an existing Jackson compatibility test project “jackson-compat-minor” (used to check cross-component compatibility across minor versions) and simply override version of Guava being used, for given Jackson minor version. While this does not conclusively prove there are no issues, it does verify that Guava module loads successfully and can read and write some basic Guava types (specifically, immutable collections). I also verified that some old versions do fail to load module (version below lowest determined as supported), to show that the setup can detect (some) negative cases.

Resulting set of version ranges is included on Jackson-datatype-guava README but here is the snapshot of the latest results:

That is:

  • Jackson 2.12 Guava module works with Guava versions from 14.0 (released in 2013) all the way to 30.1-jre (latest released Dec 2020); it declares Guava 21.0 as the “preferred” version via Maven dependencies
  • and so on for all tested Jackson versions (2.9–2.12)

So as far as I know, all recent Jackson versions support all Guava versions released during last 8 years, which seems sufficient (Guava itself was first released in 2010). :)

… it would of course be great to somehow automate such testing. But that’d be subject of another blog post. :)

Open Source developer, most known for Jackson data processor (nee “JSON library”), author of many, many other OSS libraries for Java, from ClassMate to Woodstox