Clojure Java Interoperability

Clojure has several forms and macros to call Java code. However calling Clojure code from Java is not always so straight forward. The following post shows the different options currently available.

Using gen-class

Clojure code can be compiled to standard JVM bytecode using gen-class.

Adding static modifiers

Clojure imposes the notion of immutability. As such Clojure functions are/should be void of any state or side-effects and only operate on the given input. Therefore exporting Clojure functions as static Java methods makes sense. The following example defines a Clojure function, a corresponding Java-callable function and exports the Java function as a static Method in the class com.example.Computation.

(ns com.example.computation
  (:gen-class
    :name com.example.Computation
    :methods [#^{:static true} [incrementRange [int] java.util.List]]))

(defn increment-range
  "Creates a sequence of numbers up to max and then increments them."
  [max]
  (map inc (take max (range))))

(defn -incrementRange
  "A Java-callable wrapper around the 'increment-range' function."
  [max]
  (increment-range max))

Note that the above definition could be replaced by (range start end). The example is just used to show some more Clojure functions.

The Java wrapper has to follow the standard rules for method names. Therefore increment-range has to be renamed to incrementRange (or some similar name without the - in it). The - prefix for the Java wrapper can be configured inside the :gen-class form and will be removed once gen-class runs. The usage from Java is thus:

package com.example

public class ClojureJavaInteropStatic {

    public static void main(String[] args) {
        List incrementedRange = Computation.incrementRange(10);
    }

}

Adding generics

You may notice that the returned list in the above code is raw, because. the method definition doesn’t use generics. To solve this problem declare that the generated class :implements a certain interface that exposes the desired method definition(s). You won’t be able to declare your methods as static anymore, but get a generified method for all your Java needs.

The Java interface:

package com.example

public interface RangeIncrementer {
  List<Long> incrementRange(int max);
}

The changed Clojure namespace:

(ns com.example.computation
  (:gen-class
    :name com.example.Computation
    :implements [com.example.RangeIncrementer]))

(defn increment-range
  "Creates a sequence of numbers up to max and then increments them."
  [max]
  (map inc (take max (range))))

(defn -incrementRange
  "A Java-callable wrapper around the 'increment-range' function."
  [this max]
  (increment-range max))

Finally the generified usage from Java:

package com.example

public class ClojureJavaInteropGenerics {

    public static void main(String[] args) {
        RangeIncrementer incrementer = new Computation();
        List<Long> incrementedRange = incrementer.incrementRange(10);
    }

}

Couple of notes for this as well: First the generated class still only returns the raw type (List instead of List<Integer>). So instead of using the class, use the interface for the variable declaration (RangeIncrementer incrementer = .. instead of Computation comp = ..). The interface will return the non-raw List. Second the function definition for -incrementRange is now slightly different. It needs an additional parameter (this) which exposes the current instance to the generated class/method.

Returning an array of something is also possible with the following construct "[Ljava.lang.Object;". Need a 2-dim array? Just use "[[Ljava.lang.Object;" (notice the extra [) and so on. However be aware that the method return types have to match, e. g. you can’t specify a return type of array if your Clojure function doesn’t return an array. In the example above the call to map returns LazySeq which itself is a java.util.List. Therefore the method declaration is valid and you won’t get any ClassCastException when calling incrementRange from Java.

Make your life easier with macros

Instead of defining every Clojure function which should be exported twice (the real function + the Java wrapper), it is possible to use a macro to do that extra work automatically.

(require '[clojure.string :as string)

(defn camel-case [input]
  (let [words (string/split input #"[\s_-]+")]
    (string/join (cons (string/lower-case (first words)) (map string/capitalize (rest words))))))

(defn java-name [clojure-name]
  (symbol (str "-" (camel-case (str clojure-name)))))

(defmacro defn* [name & declarations]
  (let [java-name (java-name name)]
    `(do (defn ~name ~declarations)
       (defn ~java-name ~declarations))))

The macro defn* replaces defn and automatically creates a second function with a valid camel-cased Java method name. The macro is available as a small library at Maven Central . The macro won’t add the extra parameter mentioned above to Java wrapper, so it is only useful for declaring static methods.

Using the Clojure Runtime

Using gen-class imposes certain limitations on calling Clojure code from Java. One of those are functions which make use of Clojure parameter destructuring. To invoke those functions you have to use the Clojure runtime.

// The Clojure 'require' function from the 'clojure.core' namespace.
Var require = RT.var("clojure.core", "require");

// Your namespace
Symbol namespace = Symbol.intern("DESIRED.NAMESPACE.HERE");

// Your function
Var function = RT.var("DESIRED.NAMESPACE.HERE", "DESIRED-FUNCTION");

// The required keyword for the above function
Keyword keyword = Keyword.intern("REQUIRED-KEYWORD");

// Require/Import your namespace
require.invoke(namespace);

// Invoke your function with the given keyword and its value
Object result = function.invoke(keyword, VALUE);

The desired namespace has to be on the classpath for this to work. Alternatively it is possible to load an entire Clojure script, as shown in the following example:

RT.loadResourceScript("DESIRED/NAMESPACE/HERE.clj");
RT.var("DESIRED.NAMESPACE.HERE", "DESIRED-FUNCTION").invoke(PARAMETER);

On a big project it is properly wise to move Java→Clojure interop code into helper classes/methods. Look here for an example.