Skip to content

Clojure Error Handling Guide — Try-Catch, Exceptions, and Functional Error Patterns

DodaTech Updated 2026-06-28 2 min read

In this tutorial, you will learn about Clojure Error Handling Guide. We cover key concepts, practical examples, and best practices to help you master this topic.

Clojure error handling integrates with Java Exceptions through try/catch/finally/throw -- supporting exception type matching, custom exceptions, and functional alternatives like returning nil or using Either patterns to avoid exception-based control flow.

Try-Catch

;; Basic try-catch
(try
  (/ 1 0)
  (catch ArithmeticException e
    (str "Cannot divide by zero: " (.getMessage e))))

;; Multiple exception types
(try
  (some-risky-function)
  (catch ArithmeticException e
    "Math error")
  (catch Exception e
    (str "Other error: " (.getMessage e))))

;; Finally block
(try
  (open-resource)
  (catch Exception e
    (log-error e))
  (finally
    (close-resource)))

Throwing

;; Throw an exception
(defn divide [a b]
  (if (zero? b)
    (throw (IllegalArgumentException. "Division by zero"))
    (/ a b)))

;; Custom exceptions
(defn validate-age [age]
  (if (or (< age 0) (> age 150))
    (throw (ex-info "Invalid age" {:age age :min 0 :max 150}))
    age))

;; Catch custom
(try
  (validate-age -1)
  (catch Exception e
    (ex-data e)))  ;; {:age -1, :min 0, :max 150}

Functional Error Patterns

;; Returning nil for expected failures
(defn safe-divide [a b]
  (when-not (zero? b)
    (/ a b)))

;; Either pattern
(defn safe-divide-2 [a b]
  (if (zero? b)
    {:error "Division by zero"}
    {:result (/ a b)}))

;; Using core.async for error channels
(require '[clojure.core.async :as async])

(defn process-async [input]
  (let [result (async/chan)]
    (async/go
      (try
        (async/>! result (process input))
        (catch Exception e
          (async/>! result {:error e}))))
    result))

Error Logging

;; Logging errors
(defn with-logging [f]
  (fn [& args]
    (try
      (apply f args)
      (catch Exception e
        (println "Error in" (pr-str f) "with args" (pr-str args))
        (println (.getMessage e))
        (throw e)))))

;; Wrapped function
(def safe-divide (with-logging /))

Common Mistakes

1. Using exceptions for control flow

Exceptions are for exceptional conditions. Use nil or return values for expected failures.

2. Catching too broadly

(catch Exception e ...) catches everything. Be specific with exception types.

3. Forgetting finally for resources

Always use finally for cleanup. Database connections, file handles, and locks need guaranteed release.

Practice Questions

1. How do you catch an exception in Clojure? (try ... (catch ExceptionType e ...) (finally ...)). Java interop provides exception types.

2. How do you create a custom exception with data? (ex-info "message" {:key val}). Access with (ex-data exception).

3. What is a functional alternative to exceptions? Return nil for failure, or return a map with :error key. Use the Maybe or Either pattern.

FAQ

{{< faq question="Can I catch multiple exception types?" >}} Yes. Multiple catch blocks with different types: (catch ArithmeticException e ...) (catch Exception e ...). {{< /faq >}}

{{< faq question="What is ex-info?" >}} Creates an ExceptionInfo (a clojure.lang.ExceptionInfo) with a message and associated data map. {{< /faq >}}

{{< faq question="How do I get the stack trace?" >}} (.printStackTrace e) or (clojure.repl/pst e) for a pretty-printed stack trace. {{< /faq >}}

What's Next

Now learn about testing in Clojure.

Topic Description Link
Testing Testing framework {{< ref "13-testing" >}}
Java Interop Advanced Java interop {{< ref "14-java-interop" >}}

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro