This tutorial covers creating a native binary from a Clojure hello world program using GraalVM.
Download and install the most current GraalVM Community Edition for your operating system. We use Java 23 in this tutorial.
❗
|
Ensure that your $ native-image --version |
💡
|
You can optionally use a tool like SDKMAN! to download and install GraalVM: |
# browse available versions
$ sdk list java
# install relevant versions
$ sdk use java 21.0.2-graalce
$ sdk use java 23.0.1-graal
# example aliases to switch jvm and setup env variables
$ alias graal21='sdk use java 21.0.2-graalce'
$ alias graal23='sdk use java 23.0.1-graal'
# enable Java 23 in current terminal
$ graal23
Using java version 23.0.1-graal in this shell.
$ type native-image
native-image is ~/.sdkman/candidates/java/23.0.1-graal/bin/native-image
💡
|
You may be interested in watching @borkdude’s GraalVM native Clojure: hello world video. It is now a bit dated, but walks you through this process and includes interesting observations and tips. |
We assume you have the current clojure
CLI installed.
$ mkdir -p hello-world-clj/src/hello_world
$ cd hello-world-clj
Create a deps.edn
.
Specify current versions of:
-
Clojure to take advantage of any and all GraalVM fixes.
-
graal-build-time to automatically initialize Clojure classes
{:deps {org.clojure/clojure {:mvn/version "1.12.0"}
com.github.clj-easy/graal-build-time {:mvn/version "1.0.5"}}}
Paste the following code into a src/hello_world/main.clj
file:
(ns hello-world.main
(:gen-class))
(defn -main [& _args]
(println "Hello world!"))
Run your hello world program using clojure
to prove to yourself that it works:
$ clojure -M -m hello-world.main
Hello world!
Create a classes
folder. This is where Clojure compiles source files to .class
files:
$ mkdir classes
Then run:
$ clojure -M -e "(compile 'hello-world.main)"
to compile the main namespace (and transitively everything it requires).
Verify that the program works when run from the JVM using compiled classes:
$ java -cp $(clojure -Spath):classes hello_world.main
Hello world!
Run the following to create a native image:
$ native-image \
-cp "$(clojure -Spath):classes" \
-H:Name=hello-world \
-H:+ReportExceptionStackTraces \
--features=clj_easy.graal_build_time.InitClojureClasses \
--verbose \
--no-fallback \
hello_world.main
ℹ️
|
we reference our built classes via -cp
|
This creates hello-world
, a native image version of hello world program.
Another approach is to build an uberjar with leiningen
first.
$ mkdir -p hello-world-lein/src/hello_world
$ cd hello-world-lein
Create a project.clj
.
Specify current versions of:
-
Clojure to take advantage of any and all GraalVM fixes.
-
graal-build-time to automatically initialize Clojure classes
(defproject hello-world "0.1.0-SNAPSHOT"
:dependencies [[org.clojure/clojure "1.12.0"]
[com.github.clj-easy/graal-build-time "1.0.5"]]
:main hello-world.main
:aot :all)
Paste the following code into a src/hello_world/main.clj
file:
(ns hello-world.main
(:gen-class))
(defn -main [& _args]
(println "Hello world!"))
Run your hello world program using lein
to prove to yourself that it works:
$ lein run
Hello world!
$ lein uberjar
Verify that the uberjar works when run from the JVM:
$ java -jar target/hello-world-0.1.0-SNAPSHOT-standalone.jar
Hello world!
Run the following to create a native image:
native-image \
-jar target/hello-world-0.1.0-SNAPSHOT-standalone.jar \
-H:Name=hello-world \
-H:+ReportExceptionStackTraces \
--features=clj_easy.graal_build_time.InitClojureClasses \
--verbose \
--no-fallback
ℹ️
|
we reference our built jar via -jar .
|
This creates hello-world
, a native image for your program.
Our hello world examples are designed to get you started. Here are some real world compile script examples from the wild:
And be sure to read our tips and tricks and share back your discoveries!