Skip to content
Java Interop
step 1/7

Reading — step 1 of 7

Learn

~2 min readcore.async, Java Interop, Performance

Clojure runs on the JVM and interops seamlessly with Java. "Clojure for the Brave and True" treats interop as essential — you'll use Java libraries constantly.

Calling Java methods

(.toUpperCase "hello")            ;; "HELLO"
(. "hello" toUpperCase)            ;; equivalent older syntax
(.charAt "hello" 1)                ;; \e
(.substring "hello world" 6 11)    ;; "world"

(.method object args) is the dot-method call. The dot at the front is critical — (.toUpperCase ...) means "call the toUpperCase method."

Static methods and fields

(Math/sqrt 16)            ;; 4.0
(Math/PI)                 ;; 3.141592653589793
(System/getenv "HOME")     ;; environment variable
(Integer/parseInt "42")    ;; 42

Class/method for static methods and fields. The slash syntax marks the static-ness.

Constructors

(java.util.ArrayList. )            ;; empty ArrayList
(java.util.ArrayList. [1 2 3])     ;; with initial elements
(StringBuilder. "prefix-")          ;; new StringBuilder

(Class. args) is constructor sugar — equivalent to (new Class args).

Method chains — .. and doto

;; Method chain via .. macro:
(.. (java.util.ArrayList. [1 2 3])
    (subList 0 2)
    (toString))
;; "[1, 2]"

;; Side-effectful builder pattern via doto:
(doto (StringBuilder.)
    (.append "Hello")
    (.append ", ")
    (.append "World"))

.. chains method calls (each operating on the previous result). doto calls multiple methods on the same object — returns the object for chaining.

Type hints — performance

Without hints, Clojure's compiler uses reflection on Java calls — slow:

(defn upper [s]
    (.toUpperCase s))                  ;; reflection on every call

(defn upper [^String s]
    (.toUpperCase s))                  ;; direct call — much faster

The ^String annotation tells the compiler s is a String. Add type hints in hot paths.

Enable warnings:

(set! *warn-on-reflection* true)

The REPL/compiler then prints warnings for un-hinted reflective calls.

Common Java types and helpers

;; String:
(.startsWith "hello" "he")           ;; true
(clojure.string/upper-case "hello")  ;; preferred over Java method

;; Map:
(java.util.HashMap.)
(into {} (java.util.HashMap. {"a" 1, "b" 2}))    ;; convert to Clojure map

;; Date/time:
(java.time.Instant/now)
(.format (java.time.LocalDate/now)
         (java.time.format.DateTimeFormatter/ISO_DATE))

;; Random:
(.nextInt (java.util.Random.) 100)    ;; 0-99

Implementing Java interfaces

(def my-runnable
    (reify java.lang.Runnable
        (run [_] (println "running"))))

(.start (Thread. my-runnable))

reify produces an anonymous instance implementing the given interfaces.

For a class with name and fields, defrecord or deftype.

When use Java interop

  • Calling existing Java libraries (huge ecosystem)
  • Performance-critical numeric work (primitives)
  • Platform-specific features (file system, networking, etc.)
  • Specific classes the Java ecosystem provides (e.g., java.time)

Common mistakes

  • Forgetting the leading dot(.toUpperCase "hello") is method call; (toUpperCase "hello") looks for a Clojure function.
  • Reflection in hot paths — add type hints. Massive perf wins.
  • Mixing Java and Clojure mutability — Java collections are mutable; Clojure ones aren't. Convert at boundaries.
  • .method object args — args come AFTER the object. Common newcomer error to put them before.
  • Calling instance methods on nil — NullPointerException. Same as Java; clojure isn't nil-safe at interop boundaries.

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…