Modular Java was introduced in JDK 9, and considered to be the largest architectural change
in Java, ever. The core of Java libraries were split into several modules, sits in $JAVA_HOME/jmods
.
The bloated, monolithic rt.jar
is no more.
Modular Java requires you to include a module-info.java
for a module. As of
Clojure 1.9, there is no plan (AFAIK) for module support. However, we can still
benefit from the new architecture. By using the jlink
tool, we can create a
customized Java runtime, contains certain modules. The minimal one, which has
java.base
module, is only 29MB. And this JRE, is capable for running most of
Clojure application. You can still use its java
executable as before.
I created lein-jlink to manage these
kind of customized JRE for clojure development. Put lein-jlink
in :plugins
of your project.clj
:
(defproject jlinktest "0.1.0-SNAPSHOT"
...
:plugins [lein-jlink "0.2.0-SNAPSHOT"])
By running lein jlink init
, a default (minimal) JRE is created in target/default/jlink
.
(the target path contains your profile name)
Now, adding a hello world ring app to our application:
in project.clj
:dependencies [[org.clojure/clojure "1.9.0"]
[info.sunng/ring-jetty9-adapter "0.10.4"]]
in src/jlinktest/core.clj
:
(ns jlinktest.core
(:gen-class)
(:require [ring.adapter.jetty9 :refer [run-jetty]]))
(defn app [req]
"<h1>It works</h1>")
(defn -main
"I don't do a whole lot ... yet."
[& args]
(run-jetty app {:port 8080}))
This app can be run with lein jlink run
, which uses the java
executable from
our customized JRE.
$ lein jlink run
Defaulting Uptime to NOIMPL due to (java.lang.UnsupportedOperationException) Implementation not available in this environment
2018-01-28 23:51:12.571:INFO::main: Logging initialized @-1ms to org.eclipse.jetty.util.log.StdErrLog
2018-01-28 23:51:12.695:INFO:oejs.Server:main: jetty-9.4.8.v20171121, build timestamp: 2017-11-22T05:27:37+08:00, git hash: 82b8fb23f757335bb3329d540ce37a2a2615f0a8
2018-01-28 23:51:12.742:INFO:oejs.AbstractConnector:main: Started ServerConnector@2e645fbd{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
2018-01-28 23:51:12.744:INFO:oejs.Server:main: Started @-1ms
This environment has no java.util.logging
package so you can see Jetty is
fallback to stderr for logging. But it still fully functional on serving http
requests.
To visualize the benefit on distributing our Clojure app, we can assemble a
distributable environment, using lein jlink assemble
. It runs lein uberjar
and copy the jar into JRE directory, then put a shortcut script for running our
app.
If you care about the size of your application, it's only 37MB totally for this hello world ring web application, includes runtime.
This path can be added into a docker base image, like ubuntu
:
FROM ubuntu:16.04
ADD target/default/jlink /opt/jlinktest
ENTRYPOINT /opt/jlinktest/bin/jlinktest
No need for installing openjdk-8-jre. The result image size is 159MB, the ubuntu base takes 121MB from it.
Note that you may want to run it on a minimal base like alpine
. alpine
is
based on musl-libc. If your development environment is glibc based (in most this
is true), it won't work on alpine
. We will need to use a glibc variant and
add required packages:
FROM frolvlad/alpine-glibc
RUN apk add --no-cache libstdc++
ADD target/default/jlink /opt/jlinktest
ENTRYPOINT /opt/jlinktest/bin/jlinktest
This runnable image size is, just 50.7MB!
So with jlink and modular Java, you can ship your Clojure app with this minimal runtime. This could change Java ecosystem and enable even more use cases for it.