Skip to content
Exceptions and try/catch
step 1/7

Reading — step 1 of 7

Learn

~2 min readFunctional Combinators, Sets, Errors

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…