Java Interop
Prerequisites: Namespaces, Error handling
Clojure was designed to use the host platform directly. Any Java class — from the JDK or a library — is available with a small, consistent syntax. If you know some Java, you can use all of it from Clojure.
The syntax
(println (.toUpperCase "clojure")) ; instance method
(println (.substring "clojure" 0 3)) ; with arguments
(println (Math/abs -42) (Math/max 3 7)) ; static methods
(println Integer/MAX_VALUE Math/PI) ; static fields
(println (str (java.util.ArrayList. [1 2 3]))) ; constructor (note the dot)
(println (str (new java.util.HashSet [1 1 2]))) ; constructor with new
(println (.. " padded " trim toUpperCase)) ; chained calls
CLOJURE
clo
42 7
2147483647 3.141592653589793
[1, 2, 3]
[1, 2]
PADDED
println shows most Java objects as #object[ClassName 0x1a2b3c value]
(the hex part is an identity hash that changes on every run), so the
examples call str, which uses the object's own toString.
| Java | Clojure |
|---|---|
s.toUpperCase() |
(.toUpperCase s) |
Math.abs(x) |
(Math/abs x) |
new ArrayList<>() |
(ArrayList.) or (new ArrayList) |
Integer.MAX_VALUE |
Integer/MAX_VALUE |
a.b().c() |
(.. a b c) or (-> a .b .c) |
Imports and doto
(import '(java.time LocalDate DayOfWeek)
'(java.time.format DateTimeFormatter)
'(java.util ArrayList))
(def launch (LocalDate/of 2026 3 15))
(println (str (.plusWeeks launch 6)))
(println (str (.getDayOfWeek launch)) (= DayOfWeek/SUNDAY (.getDayOfWeek launch)))
(println (.format launch (DateTimeFormatter/ofPattern "dd.MM.yyyy")))
(def names (doto (ArrayList.)
(.add "Ada")
(.add "Linus")))
(println (str names) (.size names))
2026-04-26
SUNDAY true
15.03.2026
[Ada, Linus] 2
doto calls several methods on the same object and returns the object —
handy for Java APIs that mutate.
Methods as functions (Clojure 1.12)
(println (map String/.toUpperCase ["a" "b"])) ; instance method value
(println (map Long/parseLong ["1" "20"])) ; static method value
(println (map String/new ["x" "y"])) ; constructor value
(println (map #(.length ^String %) ["abc" "de"]))
(A B)
(1 20)
(x y)
(3 2)
Since 1.12 you can pass Java methods directly where a function is
expected: Class/.method for instance methods, Class/method for
static ones, and Class/new for constructors. Before 1.12 you wrapped
them in #(...).
Collections cross the boundary
(def java-list (java.util.ArrayList. [3 1 2]))
(println (map inc java-list)) ; Java collections are seqable
(println (into [] java-list))
(println (instance? java.util.List [1 2])) ; Clojure vectors ARE Java lists
(println (instance? java.util.Map {:a 1}))
(println (.get {:a 1} :a))
(java.util.Collections/sort java-list)
(println (str java-list))
(4 2 3)
[3 1 2]
true
true
1
[1, 2, 3]
Clojure collections implement Java's read-only collection interfaces,
so you can pass them to Java methods that take a List or Map (as
long as the method doesn't try to modify them).
Implementing interfaces
(def by-length
(reify java.util.Comparator
(compare [_ a b] (compare (count a) (count b)))))
(println (sort by-length ["ccc" "a" "bb"]))
(def task (reify Runnable (run [_] (println "running in" (.getName (Thread/currentThread))))))
(doto (Thread. task "worker-1") (.start) (.join))
(println (.call (fn [] :clojure-fns-are-callables)))
(a bb ccc)
running in worker-1
:clojure-fns-are-callables
reifycreates an object implementing Java interfaces (or Clojure protocols).- Clojure functions already implement
Runnable,Callable, andComparator, so you can often pass a plainfn.
Type hints and reflection
When Clojure can't tell an object's class at compile time, it finds the
method by reflection at runtime, which is slow. A type hint like
^String fixes that; (set! *warn-on-reflection* true) makes the
compiler warn you. The Performance lesson in Level 3 shows this in
detail.