How to upgrade your Clojure projects to use Java 11

Dec 11, 2018 by Daniel Compton

JDK 11 is the latest release of the JDK (Java Development Kit). There are several changes since JDK 8 that will require projects to update their dependencies or build tools. Many open source projects have resolved these issues when adding support for JDK 9 and 10. For most projects that have been updating their dependencies regularly, the upgrade process to JDK 11 shouldn’t be too difficult.

Last year I wrote a guide on upgrading Clojure projects to Java 9. This guide can be read on it’s own, but you can find some more background and context in last year’s guide too.

JDK 11

JDK 11 was released on September 25, 2018. JDK 11 is the first long term support release since JDK 8. Due to the short support lifespan of JDK 9 and 10 (six months each), and the number of breaking changes in those releases, many businesses and individuals have continued to use JDK 8.

Free public updates for Oracle’s JDK 8 end in January 2019 for commercial users, and December 2020 for personal users. After those dates, Oracle will not be providing any more free updates for JDK 8. This means that many Clojure projects will be looking to upgrade to JDK 11 soon, or investigating other JDK providers which support JDK 8. Oracle has also created a guide for updating to JDK 11. Most of the notes in the upgrade guide are not relevant to the majority of Clojure programs, but it’s worth a quick scan as well.

Checking for library upgrades

Before beginning to upgrade to JDK 11, I would recommend checking for new versions of your dependencies. These may have fixes for incompatibilities introduced in new versions of the JDK, reducing the amount of breakage you need to resolve later. It is also much easier to upgrade your libraries first and then your JDK, rather than trying to upgrade both at the same time. You can check for updates to Leiningen projects with lein-ancient, Boot projects with boot-deps, and tools.deps projects with deps-ancient. If your project is publicly available on GitHub, you can use Deps Versions to add a badge to your README to show if your dependencies are up-to-date.

java.util.Collection toArray

If you have previously upgraded to support JDK 9 or JDK 10, the main issue you are likely to face when upgrading to JDK 11 is the addition of a method in the java.util.Collection interface. A new toArray method was added which overloads the existing 1-arity method. Java and other statically typed languages on the JVM have the type information to resolve the ambiguity, but Clojure is not able to resolve this without developers adding extra type hints to specify which method to use.

Without the type hints you would get an error like this:

Exception in thread "main" java.lang.IllegalArgumentException: Must hint overloaded method: toArray, compiling:(clojure/core/rrb_vector/rrbt.clj:282:1)

This change affected several projects directly including core.rrb-vector (CRRBV-18), org.flatland/ordered (#37), datascript (#273), and Clojure (CLJ-2374). In practice, this isn’t an issue for Clojure. Clojure is distributed as an AOT compiled JAR and is compiled against older versions of Java. You were only likely to run into this issue if you were working on Clojure itself.

RRB Vector and Ordered have a much larger impact however. Many projects have dependencies or transitive dependencies on one of these libraries, including Midje, Lacinia, Fipp, Puget, lein-monolith, clj-yaml, compojure-api, and ring-middleware-format.

There are new releases for RRB Vector and Ordered: [org.clojure/core.rrb-vector "0.0.13"], and [org.flatland/ordered "1.5.7"] respectively. Many of the downstream consumers of these libraries have been updated to use these versions, but some haven’t yet.

If you are using Leiningen, this is a great use-case for the :managed-dependencies feature. If you add a vector of :managed-dependencies to your project.clj, Leiningen will choose those versions if your project depended on any version of that dependency. boot-bundle provides a similar feature for Boot.

:managed-dependencies [[org.clojure/core.rrb-vector "0.0.13"]
                       [org.flatland/ordered "1.5.7"]]

If you have any Leiningen plugins with dependencies on rrb-vector or ordered, you can override the resolved dependency by adding an explicit dependency to the :plugins vector. For example:

:plugins [[lein-monolith "1.0.1"]
          ;; Overrides older version of rrb-vector that
          ;; doesn't work on JDK 11.
          [org.clojure/core.rrb-vector "0.0.13"]]

If you can’t work out where a dependency is coming from, you can use a new feature in Lein 2.8.1: lein deps :why.

$ lein deps :why org.clojure/core.rrb-vector
[karma-reporter 3.1.0]
  [fipp 0.6.12]
    [org.clojure/core.rrb-vector 0.0.11]

Deprecations and removals

JDK 9 deprecated several Java EE and CORBA modules. The modules were still in the JDK, but not resolved by default. To get access to those modules you needed to explicitly add them back with the command line flag --add-modules, e.g. --add-modules "java.xml.bind".

Without this flag, you would get errors like:

Caused by: java.lang.ClassNotFoundException: javax.xml.bind.DatatypeConverter

The most common use of these modules in the Clojure community was the Base64 converters in javax.xml.bind. Java 8 added a Base64 class to java.util which is a suitable replacement.

If you need to support JDK’s older than Java 8, http-kit shows a way of using macros to support both methods. Note that if you do this and AOT compile your applications, make sure that you compile with the same version of Java you’ll be running in production.

JDK 9 also removed several of the sun.* APIs, including sun.misc.BASE64Encoder and sun.misc.BASE64Decoder. Again, the suggested migration path is to use java.util.Base64.

JDK 11 removed the Java EE and CORBA modules. If you or your dependencies were still using these modules, you will need to add them explicitly as dependencies. JEP 320 has more information on the removed modules and where to get their replacements. If you’re using a library that expects these dependencies, you will need to add a dependency on the JAR to download it from Maven Central. For example:

[javax.xml.bind/jaxb-api "2.4.0-b180830.0359"]

Build Tools

Leiningen 2.8.0 or later is required to use JDK 11. You can get the latest version of Leiningen by running lein upgrade. At the time of writing, Leiningen 2.8.1 was the latest version available.

Boot has been updated to 2.8.2 to build under JDK 11. However, in my testing, it seems that Boot 2.6.0 and up can run under JDK 11. 2.8.2 was released on 14 September 2018. Update boot with boot -u to get the latest version. At the time of writing, Boot 2.8.2 was the latest version.

tools.deps appears to have no blocking issues running JDK 11. There is an open issue about JDK 9 and up adding spurious newlines to a POM, but this is unlikely to be an issue for most people.

If you run into any issues with any of these build tools, let me know, and I’ll update this post.

Clojure and ClojureScript versions

Clojure and ClojureScript both have fixes to improve compatibility with JDK 9 and up. ClojureScript 1.10.63 and up include CLJS-2377 which avoids depending on java.xml.bind.

Clojure 1.9-beta1 has support for running on the bootclasspath under Java 9. If you can’t upgrade Leiningen to 2.8.1, then upgrading Clojure to 1.9-beta1 or later should also work.

Keeping up to date

Java’s new six-monthly release cycle has increased the rate of change in the JDK, and the number of versions that library consumers may be using. The increased willingness to deprecate and extract parts of the JDK means that many developers should consider testing against early access versions of the JDK to ensure that they aren’t caught off-guard. If you’re using Travis CI, I’ve written a guide on testing against the latest JDK.