Reading — step 1 of 7
Learn
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…