Reading — step 1 of 7
Learn
Clojure runs on the JVM and uses Java's exception machinery. The syntax is similar to Java but Clojure-flavored. Used for genuinely exceptional conditions; preferred error style is values (Either-like patterns).
try/catch/finally
(try
(parse-config input)
(catch java.io.FileNotFoundException e
(println "missing")
nil)
(catch Exception e
(println "failed:" (.getMessage e))
nil)
(finally
(cleanup)))
(catch ExceptionClass e ...)— multiple allowed, most-specific first(finally ...)— always runs, regardless of success/failure- Catches Java exceptions (Clojure exceptions are Java exceptions)
Throwing exceptions
(throw (Exception. "something failed"))
(throw (IllegalArgumentException. "bad input"))
(throw (RuntimeException. "runtime error"))
(Class. args) is constructor sugar — Exception. calls (new Exception ...).
ex-info — Clojure-style errors with data
(throw (ex-info "validation failed"
{:field :email
:reason :missing-at}))
;; In a catch:
(try
(validate user)
(catch clojure.lang.ExceptionInfo e
(let [data (ex-data e)]
(println "field:" (:field data)))))
ex-info creates a special exception that carries a map of structured data. ex-data extracts the map. This is the Clojure-idiomatic way to throw rich errors — better than embedding info in the message string.
Catching multiple types
No built-in syntax for multi-catch like Java 7+. Patterns:
;; Either separate clauses:
(try (work)
(catch IOException e ...)
(catch SQLException e ...))
;; Or catch broader and dispatch:
(try (work)
(catch Exception e
(cond
(instance? IOException e) ...
(instance? SQLException e) ...
:else (throw e))))
The first form is preferred; multi-catch via cond is a smell.
Idiomatic alternative: return values
Most Clojure code prefers returning [result error] or {:ok value} / {:error reason} shapes over throwing:
(defn parse-age [s]
(try
(let [n (Integer/parseInt s)]
(if (neg? n)
{:error :negative}
{:ok n}))
(catch NumberFormatException _
{:error :not-a-number})))
;; Caller:
(let [{:keys [ok error]} (parse-age "42")]
(if ok
(println "got" ok)
(println "failed:" error)))
For expected failure modes (parse errors, validation), return values. Reserve exceptions for genuinely exceptional conditions (file not found, network down).
try-as-expression
try is an expression — evaluates to the result of the body or the matching catch:
(let [result (try
(parse "42")
(catch Exception _ :error))]
(case result
:error "failed"
result))
Useful for inline handling.
Common mistakes
- Throwing strings —
(throw "oops")doesn't work. Must throw a Throwable subclass. - Catching Throwable carelessly — catches OutOfMemoryError, ThreadDeath, etc. Usually want Exception.
- Re-throwing without preserving the original — use
(throw e)to re-raise, or wrap with(throw (ex-info "context" {} e))(the third arg of ex-info is a cause). - Using exceptions for flow control — slow and obscures intent. Return values for expected failures.
- Not using ex-info — throw maps of data instead of strings; let callers pattern-match on shape.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…