A Dependency Hell issue in Clojure

A Dependency Hell issue in Clojure


The story

We have a Clojure application that uses Jetty and Ring to serve itself as a web service. Recently, I’m thinking about adding a Swagger UI (or OpenAPI) to it, without having to manage a lot of Swagger assets by ourselves. luckily since we are using reitit as the server router, it provides some built-in support for Swagger, even though it only supports up to Swagger 2.0. Everything is smooth until suddenly we hit a snag when trying to make it work:

  1. Following the instructions here: reitit docs for adding Swagger
  2. When try to boot build, some errors popped up:
    java.lang.ClassNotFoundException: com.fasterxml.jackson.core.exc.InputCoercionException
    java.lang.NoClassDefFoundError: com/fasterxml/jackson/core/exc/InputCoercionException
    clojure.lang.ExceptionInfo: com/fasterxml/jackson/core/exc/InputCoercionException
  3. There is a few useful related information for debugging:
  4. By running lein deps :tree and looking at the results, it turns out the vault-clj library is using cheshire, which specifies a different version of com.fasterxml.jackson.core/jackson-coreas its dependency from reitit’s. Here is the related parts of the output:
    [amperity/vault-clj "0.5.1"]
    [amperity/envoy "0.3.1"]
      [environ "1.1.0"]
      [table "0.5.0"]
    [cheshire "5.7.1"]
      [com.fasterxml.jackson.core/jackson-core "2.8.6"]
      [com.fasterxml.jackson.dataformat/jackson-dataformat-cbor "2.8.6"]
      [com.fasterxml.jackson.dataformat/jackson-dataformat-smile "2.8.6"]
      [tigris "0.1.1"]
    [com.stuartsierra/component "0.3.2"]
      [com.stuartsierra/dependency "0.2.0"]
    [org.clojure/tools.logging "0.3.1"]
    [metosin/reitit-swagger-ui "0.3.10"]
    [metosin/jsonista "0.2.5"]
      [com.fasterxml.jackson.core/jackson-databind "2.10.0"]
        [com.fasterxml.jackson.core/jackson-annotations "2.10.0"]
      [com.fasterxml.jackson.datatype/jackson-datatype-jsr310 "2.10.0"]
    [metosin/ring-swagger-ui "2.2.10"]
  5. Looking at cheshire’s issue, it seems even the latest version of cheshire does not point to jackson.core "2.10.0" yet.
  6. It is awkward, but for now the easiet solution for me is to force specify the version of jackson.core in my boot.build file, part of which now looks like this:
     '[[com.fasterxml.jackson.core/jackson-core "2.10.0"]
      [amperity/vault-clj                "0.5.1"]
      [metosin/reitit                    "0.3.10"]
      [metosin/reitit-swagger            "0.3.10"]
      [metosin/reitit-swagger-ui         "0.3.10"]])

    Apparently, most of people who have run into this “Jackson” error ended up listing it explicitly like what I did… Check here for more discusssions around this on Clojureverse

One more thing

Different languages/communities have different ways to manage the dependecies, e.g. JavaScript has the package-lock.json, Python is still figuring out (there is a great potential tool I really liked: poetry, but it’s still not yet widely adopted). Someone has proved that the dependency hell is a NP-Complete problem. While people are actively trying to improve on this problem, we should really stick with a minimum set of dependencies and avoid using unncessary tools in our projects, i.e. don’t re-invent wheels, but always use the indispensable wheels.