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:

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.

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: