Calling Clojure from Java

Calling clojure from java

Update: Since this answer was posted, some of the tools available have changed. After the original answer, there is an update including information on how to build the example with current tools.

It isn't quite as simple as compiling to a jar and calling the internal methods. There do seem to be a few tricks to make it all work though. Here's an example of a simple Clojure file that can be compiled to a jar:

(ns com.domain.tiny
(:gen-class
:name com.domain.tiny
:methods [#^{:static true} [binomial [int int] double]]))

(defn binomial
"Calculate the binomial coefficient."
[n k]
(let [a (inc n)]
(loop [b 1
c 1]
(if (> b k)
c
(recur (inc b) (* (/ (- a b) b) c))))))

(defn -binomial
"A Java-callable wrapper around the 'binomial' function."
[n k]
(binomial n k))

(defn -main []
(println (str "(binomial 5 3): " (binomial 5 3)))
(println (str "(binomial 10042 111): " (binomial 10042 111)))
)

If you run it, you should see something like:

(binomial 5 3): 10
(binomial 10042 111): 49068389575068144946633777...

And here's a Java program that calls the -binomial function in the tiny.jar.

import com.domain.tiny;

public class Main {

public static void main(String[] args) {
System.out.println("(binomial 5 3): " + tiny.binomial(5, 3));
System.out.println("(binomial 10042, 111): " + tiny.binomial(10042, 111));
}
}

It's output is:

(binomial 5 3): 10.0
(binomial 10042, 111): 4.9068389575068143E263

The first piece of magic is using the :methods keyword in the gen-class statement. That seems to be required to let you access the Clojure function something like static methods in Java.

The second thing is to create a wrapper function that can be called by Java. Notice that the second version of -binomial has a dash in front of it.

And of course the Clojure jar itself must be on the class path. This example used the Clojure-1.1.0 jar.

Update: This answer has been re-tested using the following tools:

  • Clojure 1.5.1
  • Leiningen 2.1.3
  • JDK 1.7.0 Update 25

The Clojure Part

First create a project and associated directory structure using Leiningen:

C:\projects>lein new com.domain.tiny

Now, change to the project directory.

C:\projects>cd com.domain.tiny

In the project directory, open the project.clj file and edit it such that the contents are as shown below.

(defproject com.domain.tiny "0.1.0-SNAPSHOT"
:description "An example of stand alone Clojure-Java interop"
:url "http://clarkonium.net/2013/06/java-clojure-interop-an-update/"
:license {:name "Eclipse Public License"
:url "http://www.eclipse.org/legal/epl-v10.html"}
:dependencies [[org.clojure/clojure "1.5.1"]]
:aot :all
:main com.domain.tiny)

Now, make sure all of the dependencies (Clojure) are available.

C:\projects\com.domain.tiny>lein deps

You may see a message about downloading the Clojure jar at this point.

Now edit the Clojure file C:\projects\com.domain.tiny\src\com\domain\tiny.clj such that it contains the Clojure program shown in the original answer. (This file was created when Leiningen created the project.)

Much of the magic here is in the namespace declaration. The :gen-class tells the system to create a class named com.domain.tiny with a single static method called binomial, a function taking two integer arguments and returning a double. There are two similarly named functions binomial, a traditional Clojure function, and -binomial and wrapper accessible from Java. Note the hyphen in the function name -binomial. The default prefix is a hyphen, but it can be changed to something else if desired. The -main function just makes a couple of calls to the binomial function to assure that we are getting the correct results. To do that, compile the class and run the program.

C:\projects\com.domain.tiny>lein run

You should see output shown in the original answer.

Now package it up in a jar and put it someplace convenient. Copy the Clojure jar there too.

C:\projects\com.domain.tiny>lein jar
Created C:\projects\com.domain.tiny\target\com.domain.tiny-0.1.0-SNAPSHOT.jar
C:\projects\com.domain.tiny>mkdir \target\lib

C:\projects\com.domain.tiny>copy target\com.domain.tiny-0.1.0-SNAPSHOT.jar target\lib\
1 file(s) copied.

C:\projects\com.domain.tiny>copy "C:<path to clojure jar>\clojure-1.5.1.jar" target\lib\
1 file(s) copied.

The Java Part

Leiningen has a built-in task, lein-javac, that should be able to help with the Java compilation. Unfortunately, it seems to be broken in version 2.1.3. It can't find the installed JDK and it can't find the Maven repository. The paths to both have embedded spaces on my system. I assume that is the problem. Any Java IDE could handle the compilation and packaging too. But for this post, we're going old school and doing it at the command line.

First create the file Main.java with the contents shown in the original answer.

To compile java part

javac -g -cp target\com.domain.tiny-0.1.0-SNAPSHOT.jar -d target\src\com\domain\Main.java

Now create a file with some meta-information to add to the jar we want to build. In Manifest.txt, add the following text

Class-Path: lib\com.domain.tiny-0.1.0-SNAPSHOT.jar lib\clojure-1.5.1.jar
Main-Class: Main

Now package it all up into one big jar file, including our Clojure program and the Clojure jar.

C:\projects\com.domain.tiny\target>jar cfm Interop.jar Manifest.txt Main.class lib\com.domain.tiny-0.1.0-SNAPSHOT.jar lib\clojure-1.5.1.jar

To run the program:

C:\projects\com.domain.tiny\target>java -jar Interop.jar
(binomial 5 3): 10.0
(binomial 10042, 111): 4.9068389575068143E263

The output is essentially identical to that produced by Clojure alone, but the result has been converted to a Java double.

As mentioned, a Java IDE will probably take care of the messy compilation arguments and the packaging.

Calling Clojure from Java: Why is the new style (clojure.java.api.Clojure) better than the old one (gen-class)?

You are sorta setting up a false dichotomy here. Every approach involves creating a jar file: that is just how JVM programs are distributed. But there are 3 different ways for Java code to invoke Clojure code contained in a jar:

  1. Use methods in clojure.lang.RT to initialize the runtime, load files, and then look up vars. This is the old, deprecated approach.
  2. Use methods in clojure.java.api.Clojure to look up functions and invoke them. This is the newer version of (1), and hides some of the messy stuff you could accidentally get wrong.
  3. Use gen-class in the Clojure library to define a more Java-friendly interface to the Clojure functions.

You can still do (3) - there's nothing wrong with it exactly. But gen-class is a pretty clunky tool, and except for the simplest examples like exposing a number of static methods, it's just not a lot of fun, and it's not easy to provide an API that "feels" like a Java API using Clojure.

But you know what's great at providing an API that feels like Java? Java! So what I recommend if you want to make a Clojure library easy to use in Java is to include some Java code in your Clojure library. That Java code, written by you, bridges the language gap. It accesses your Clojure code by mechanism (2) above, and presents a Java-friendly facade so the outside world doesn't have to know there's Clojure underneath.

amalloy/thrift-gen is an example of a library I wrote years ago following this approach. It would not be at all easy to write this in pure Clojure, just because traditional Java idioms are very foreign to Clojure, and it doesn't support them all very well. By writing my own Java shim instead, Java clients get a very comfortable interface to work with, and I can just write Clojure that feels like Clojure instead of a bunch of gen-class nonsense.

calling clojure from Java (Clojure Interop)

I followed these instructions and it seemed to resolve the error of class not found. It seems as though when running the command

mvn azure-functions:run

It doesn't automatically find all imported libraries. You either have to use

  1. maven-assembly-plugin
  2. maven-shade-plugin

Could not locate proj/core.clj on classpath when calling clojure from java

Here is a full minimal working version showing Clojure called from Java.

Project layout:

.
├── project.clj
├── resources
└── src
   ├── clojure
   │   └── proj
   │   └── core.clj
   └── java
   └── proj
   └── Main.java

project.clj:

(defproject overflow-java "0.1.0-SNAPSHOT"
:source-paths ["src/clojure"]
:java-source-paths ["src/java"]
:dependencies [[org.clojure/clojure "1.10.1"]]
:repl-options {:init-ns proj.core})

src/clojure/proj/core.clj:

(ns proj.core)

(defn foo
"I don't do a whole lot."
[x]
(println x "Hello, World!"))

src/java/proj/Main.java:

package proj;

import clojure.java.api.Clojure;
import clojure.lang.IFn;

public class Main {
public static void main(String[] args) {
IFn require = Clojure.var("clojure.core", "require");
require.invoke(Clojure.read("proj.core"));
IFn foo = Clojure.var("proj.core", "foo");
foo.invoke("Steffan");
}
}

Build uberjar and run:

$ lein uberjar
$ java -cp target/overflow-java-0.1.0-SNAPSHOT-standalone.jar proj.Main

Run output:

Steffan Hello, World!

The Leiningen docs advise putting Java and Clojure source files in their own source roots.

If you wish to run the project directly In IntelliJ you will need to configure the project module in Project Settings > Project Structure > Modules. In the "Sources" tab, select the entry src/clojure (it will be coloured blue as a "Source Folder") then click the button "Resources" to mark it as a resources root instead. This ensures the .clj files are present on the classpath when running the Java application in IntelliJ. You can now run the application by clicking on the green arrow in the gutter by Java method main in class proj.Main. You'll need to reapply this module change each time Cursive refreshes the Leiningen project, as the change does not stick.

How to invoke Clojure function directly from Java

You forgot to require the Clojure namespace:

Here is the project structure:

-rwxrwxr-x 1 alan alan  162 Sep 26 15:27 compile-run-java.bash*
-rwxrwxr-x 1 alan alan 439 Oct 19 2016 project.clj*
-rw-rw-r-- 1 alan alan 142 Sep 26 16:38 src/embedded_clojure/core.clj
-rw-rw-r-- 1 alan alan 586 Sep 26 15:21 src-java/mypkg/Main.java
-rw-rw-r-- 1 alan alan 125 Oct 19 2016 test/tst/embedded_clojure/core.clj

and the project.clj file:

(defproject embedded-clojure "0.1.0-SNAPSHOT"
:dependencies [
[org.clojure/clojure "1.8.0"]
]
:java-source-paths ["src-java"]
:main embedded-clojure.core
:target-path "target/%s"
:profiles {:uberjar {:aot :all}})

Here is the Java class that calls the embedded Clojure function:

package mypkg;
import clojure.java.api.Clojure;
import clojure.lang.IFn;
class Main {
public static void main( String[] args ) {
System.out.println( "Java Main.main()" );

// clojure.core is automatically "required"; you don't need to
IFn plus = Clojure.var("clojure.core", "+");
System.out.println( " plus: " + plus.invoke(1, 2) );

// any other namespace needs to be "required"
IFn require = Clojure.var("clojure.core", "require");
require.invoke(Clojure.read("embedded-clojure.core"));

IFn add = Clojure.var("embedded-clojure.core", "add");
System.out.println( " add: " + add.invoke(2, 3) );
}
}

Clojure file:

(ns embedded-clojure.core
(:gen-class))

(defn add [x y] (+ x y))

(defn -main [& args]
(println "Clojure -main: (add 4 5) =>" (add 4 5)))

Clojure test file:

(ns tst.embedded-clojure.core
(:use embedded-clojure.core
clojure.test))

(deftest simple-add
(is (= 13 (add 6 7))))

Run Script compile-run-java.bash

#!/bin/bash  -v

lein clean
lein uberjar
# Use Java main()
java -cp /home/alan/demo/embedded-clojure/target/uberjar/embedded-clojure-0.1.0-SNAPSHOT-standalone.jar \
mypkg.Main

# Use Clojure -main
java -cp /home/alan/demo/embedded-clojure/target/uberjar/embedded-clojure-0.1.0-SNAPSHOT-standalone.jar \
embedded_clojure/core

and now we can run from the command line:

 > ./compile-run-java.bash
#!/bin/bash -v

lein clean
lein uberjar
Compiling 1 source files to /home/alan/demo/embedded-clojure/target/uberjar/classes
Compiling embedded-clojure.core
Created /home/alan/demo/embedded-clojure/target/uberjar/embedded-clojure-0.1.0-SNAPSHOT.jar
Created /home/alan/demo/embedded-clojure/target/uberjar/embedded-clojure-0.1.0-SNAPSHOT-standalone.jar
# Use Java main()
java -cp /home/alan/demo/embedded-clojure/target/uberjar/embedded-clojure-0.1.0-SNAPSHOT-standalone.jar \
mypkg.Main
Java Main.main()
plus: 3
add: 5

# Use Clojure -main
java -cp /home/alan/demo/embedded-clojure/target/uberjar/embedded-clojure-0.1.0-SNAPSHOT-standalone.jar \
embedded_clojure/core
Clojure -main: (add 4 5) => 9

We can also use lein to run the Clojure -main or the Clojure tests:

> lein test
lein test tst.embedded-clojure.core
Ran 1 tests containing 1 assertions.
0 failures, 0 errors.

> lein run
Clojure -main: (add 4 5) => 9

Problems calling a clojure function that takes a map as parameter from java

I guess that your error is caused by this definition in :gen-class:

[non_empty_seats [java.util.Map] java.util.Map]]

From docs for :gen-class:

:methods [ [name [param-types] return-type], ...]

The expected type of returned value is java.util.Map, but filter in -filter-empty-seats returns instance of clojure.lang.LazySeq. You should rewrite -non_empty_seats like this:

(defn -non_empty_seats
[java-seats]
(into {} (-filter-empty-seats java-seats)))


Related Topics



Leave a reply



Submit