How to upgrade your Clojure projects to use Java 9

Dec 6, 2017 by Daniel Compton

Java 9 was recently released on September 21st, 2017. If you’re a Clojure developer, you’re probably keen to try it out to see some of the new features and performance improvements available. To do so, you need to do a few things. For most projects, this will be a small to moderate amount of work.

Upgrade Leiningen

Leiningen has been updated to include support for Java 9. You need to upgrade to at least 2.8.0 to be able to use it. At the time of writing 2.8.1 is the latest version available. Run lein upgrade 2.8.1 or simply lein upgrade to get the latest version.

Upgrade Boot

Boot has also been updated to include support for Java 9. Support was added in Boot 2.7.0. This was released in December 2016, so you are probably already using it. Update boot with boot -u to get the latest version.

Add the java.xml.bind module

One of the new features in Java 9 is modules. At the time of writing, the latest release of ClojureScript depends on java.xml.bind.DataTypeConverter. The java.xml.bind package has been deprecated in Java 9 and put inside a non-default module. If you try to compile a ClojureScript project without adding this module, you will get an error like this:

<Exception details>
...
Caused by: java.lang.ClassNotFoundException: javax.xml.bind.DatatypeConverter

To get access to it, you need to add the new JVM option --add-modules "java.xml.bind". In Leiningen you can add :jvm-opts ["--add-modules" "java.xml.bind"] to your project.clj file. CLJS-2377 tracks this issue, and it has already been resolved on master. You can also add -Djdk.launcher.addmods=java.xml.bind to the environment variable JAVA_TOOL_OPTIONS (--add-modules doesn’t work when set in JAVA_TOOL_OPTIONS). I recommend setting this with direnv to help everyone on your team use the same settings.

If you need to run your project with Leiningen on both Java 8 and Java 9 then you will run into trouble by just adding :jvm-opts ["--add-modules" "java.xml.bind"]. Depending on your version of Java 8 and Leiningen, you will get an error.

Unrecognized option: --add-modules
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.
Error encountered performing task 'do' with profile(s): 'base,system,user,provided,dev,dev-run'
Subprocess failed

This is because older JVM’s don’t understand the new option. One workaround is to detect the version of the JDK that your system is using with a bash script, and only set JAVA_TOOL_OPTIONS when 9 or up is detected. Another possibility is to add this to your project.clj and let Leiningen apply the settings when it starts up (This method was suggested by Peter Monks).

  :jvm-opts ~(let [version     (System/getProperty "java.version")
                   [major _ _] (clojure.string/split version #"\.")]
               (if (>= (java.lang.Integer/parseInt major) 9)
                 ["--add-modules" "java.xml.bind"]
                 []))

Upgrade your libraries

Toby Crawley has a list of libraries affected by old versions of Dynapath, along with other Java 9 issues. There are many other Java and Clojure dependencies affected by changes in Java 9, so it’s worth checking for updates before you upgrade. One of note is http-kit which also depends on javax.xml.bind.DatatypeConverter.

New versions of Clojure and ClojureScript

If you’ve applied the steps above, you won’t need to upgrade Clojure or ClojureScript. However they both have fixes to improve compatibility with Java 9. 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 don’t want to upgrade Leiningen to 2.8.1, then upgrading Clojure to 1.9-beta1 or higher should also work.

Conclusion

Let me know if I’ve missed anything, and I’ll update this guide further. You can also watch Toby Crawley’s excellent talk on Clojure, Java 9, and You.