For whatever reasons, you might sometimes want to develop your
program in a mix of Clojure and Java. (Or some other language that
compiles to .class
files.)
This is actually so easy that nobody has bothered with writing up an article on how to do it. At least not for non-Leiningen projects. Until now, because in processing a pull request on Calva, I had reasons to try create a project like this, and I’d like to help you walk a bit straighter of a path than I did.
TL;DR
Clojure happily imports things from Java .class
files
using the same import macro as
you import any Java things you are using. The files just need to be on
the classpath. This means that all you have to do is to make sure your
.class
files are in a directory reachable from
:paths
in your deps.edn
file. Then you can
import away.
(I am super stupid and things that are too easy can take a while for me to figure out. Often because I am overthinking things to compensate for my stupidity.)
Read on for a more complete description.
The project
Since I wasn’t going to actually use the project for something other than development experiments, I just created this directory structure:
├── clojure-java
│ ├── deps.edn
│ └── java
│ │ └── pez
│ │ └── HelloWorld.java
│ └── src
│ │ └── pez
│ | └── java_hello.clj
The full deps.edn
looks like so:
{:paths ["src" "java"]}
In the Java file I am merely exporting (making public
) a
static String
field, a static method and an instance
method:
package pez;
public class HelloWorld {
public static String FOO = "Foo";
public HelloWorld() {
}
public static void main(String[] args) {
System.out.println("Hello, World!");
}
public static String foo() {
return "foo";
}
public String bar() {
return "bar";
}
}
I compiled this like so:
$ (cd java; javac HelloWorld.java)
Which creates java/HelloWorld.class
.
And then in the Clojure code I am importing the class and using a Rich comment block to exercise the ”API”:
(ns pez.java-hello
(:import [pez HelloWorld]))
(comment
HelloWorld/FOO
(HelloWorld/foo)
(.bar (HelloWorld.))
)
Starting the project using Calva is just a matter of executing the
Jack-in command and selecting deps.edn
as the project type.
Then I can load the Clojure file and evaluate the top level forms in the
Rich comment:
That’s all there is to it. Told you it is easy! 😀
Pitfalls
Well, a pitfall at least. Figuring this out took me some extra time
because I didn’t realize that Clojure will not pick up the latest
compiled .class
file. Once you have imported it, you are
stuck with the definitions in that class file. Since I started with an
empty HelloWorld
class definition that meant that none of
my evals worked.
Once I had gotten some help with suspecting that I might be loading an old definition of my Java code, i didn’t really succeed in finding a nice workflow. The only way i could figure out to get the latest compiled code loaded was to restart the REPL. That feels very alien to someone who has been spoiled with the Clojure way. If you know a more dynamic approach to do this, please let me know. Chat me up on the Clojurian’s Slack, or e-mail me (address below), or comment on this Twitter thread:
Need to mix some Java into your Clojure project? It is really easy to do so. So easy you probably don't need this short blog post about the issue. But I wrote it anyway, because I would have needed it. 😀 #clojure https://t.co/GUWtgY3R4C
— Peter Strömberg aka PEZ (@pappapez) December 30, 2021
Beyond experiments
The simple layout of this project shows with how little ceremony you can set up a mixed JVM language project using Clojure. Though in a real project you will need some more infrastructure.
For creating the project I can recommend deps-new which sets up scaffolding for things like tests, compiling uber-jars, and more. I don’t think it caters for the mixed Java+Clojure scenario (but maybe some project template does?).
To automate the compilation and packaging of your mixed project check
this
tools.build
build script out.
Note: This article is specifically about tools-deps projects. But I should probably at least mention that Leiningen has some explicit support for this scenario. See: Polyglot (Clojure, Java) Projects With Leiningen