Skip to end of metadata
Go to start of metadata

Note: Still a WIP


  • Errors can occur in several phases but all get reported in the same way
  • Can we distinguish between these phases based on where it occurs?
  • How can we format exceptions for each kind of exception

Execution Phases

JobPhaseWhat is thrown intentionally?What is thrown accidentally?Where thrown?Detect where?Message templateExample errorExample messageMessage in 1.9
REPLRead / reading sourceIOEx, RuntimeEx, NumberFormatEx, IllegalArgumentEx, IllegalStateEx, UnsupportedOpExN/A


wrapped in ReaderException in

Catch during read in clojure.main/repl-readSyntax error reading at (LINE:COL). Cause: CAUSE:::5Syntax error reading at (1:1). Cause: Invalid token: :::5RuntimeException Invalid token: :::5 clojure.lang.Util.runtimeException (
compileRead / reading sourceIOEx, RuntimeEx, NumberFormatEx, IllegalArgumentEx, IllegalStateEx, UnsupportedOpExN/A


wrapped in ReaderException in

ReaderException replaced with CompilerException in Compiler.compile()

Catch from in Compiler.compile()

Syntax error reading source file at (SOURCE:LINE:COL). Cause: CAUSE

:::5 in .clj fileSyntax error reading source file at (foo.clj:2:0). Cause: Invalid token: :::5RuntimeException Invalid token: :::5 clojure.lang.Util.runtimeException (
REPL | compilecompile / macroexpand (spec)ExceptionInfo w/spec keysN/ACompiler.checkSpecs()Catch from checkSpecs in Compiler.macroexpand1()

Syntax error macroexpanding MACRONAME at (SOURCE:LINE:COL):

(let [x])

Syntax error macroexpanding clojure.core/let at (foo.clj:10:5):
In: [0] val: () fails spec: :clojure.core.specs.alpha/bindings at: [:args :bindings :init-expr] predicate: any?, Insufficient input

CompilerException clojure.lang.ExceptionInfo: Call to clojure.core/let did not conform to spec:
In: [0] val: () fails spec: :clojure.core.specs.alpha/bindings at: [:args :bindings :init-expr] predicate: any?, Insufficient input
#:clojure.spec.alpha{:problems [{:path [:args :bindings :init-expr], :reason ""Insufficient input"", :pred clojure.core/any?, :val (), :via [:clojure.core.specs.alpha/bindings :clojure.core.specs.alpha/bindings], :in [0]}], :spec #object[clojure.spec.alpha$regex_spec_impl$reify__2436 0x4b45dcb8 ""clojure.spec.alpha$regex_spec_impl$reify__2436@4b45dcb8""], :value ([x]), :args ([x])}, compiling:(foo.clj:10:5)

compilecompile / macroexpand (thrown by macro)thrown by core defmacro impls: IllegalArgEx, IllegalStateEx, Ex, ExInfo community is sameN/Ain macro implementations under Compiler.macroexpand()Catch during invocation of macro in Compiler.macroexpand1()Syntax error macroexpanding MACRONAME at (SOURCE:LINE:COL). Cause: CAUSE(cond 1)Syntax error macroexpanding clojure.core/cond at (foo.clj:10:5). Cause: cond requires an even number of formsIllegalArgumentException cond requires an even number of forms  clojure.core/cond (core.clj:600)
REPL | compilecompile / macroexpand (accidental throw in macro)N/Aeverything but intentional exs in prior rowin macro implementations under Compiler.macroexpand()"Unexpected error macroexpanding MACRONAME at (SOURCE:LINE:COL). Cause: EXCLASS CAUSE(defmulti 5 class)Unexpected error macroexpanding clojure.core/defmulti at (foo.clj:10:5). Cause: ClassCastException java.lang.Long cannot be cast to clojure.lang.IObjClassCastException java.lang.Long cannot be cast to clojure.lang.IObj  clojure.core/with-meta--5142 (core.clj:217)
REPL | compilecompileIOEx, RuntimeEx, NumberFormatEx, IllegalArgumentEx, IllegalStateEx, UnsupportedOpEx, ArityEx, ExceptionInfo, ReflectiveOperationExnot worrying about for now

Compiler expr parsers

wrapped in CompilerException in Compiler.compile()

Catch in analyze or eval in Compiler.compile1()Syntax error compiling EXPRNAME at (SOURCE:LINE:COL). Cause: CAUSE(def 5)Syntax error compiling def at (foo.clj:10:5). Cause: First argument to def must be a SymbolCompilerException java.lang.RuntimeException: First argument to def must be a Symbol, compiling:(foo.clj:5:1)
REPL | runtimeevaluationany (except AssertionError or spec ExceptionInfo)don't differentiateany functionCatch in call to eval in clojure.main/replEvaluation error at STACKFN (STACKSRC:LINE). EXCLASS CAUSE

(/ 1 0)
(+ 1 :a)

Evaluation error at clojure.lang.Numbers.divide ( ArithmeticException Divide by zeroArithmeticException Divide by zero  clojure.lang.Numbers.divide (
REPL | runtimeevaluation / spec instrumentExceptionInfo w/spec keysN/Ainstrumented spec"

Evaluation error at STACKFN (STACKSRC:LINE), did not conform to spec:

(f :a)

Evaluation error at user/f (foo.clj:10), did not conform to spec:
In: [0] val: :a fails at: [:args :a] predicate: int?

ExceptionInfo Call to #'user/f did not conform to spec:
In: [0] val: :a fails at: [:args :a] predicate: int?
clojure.core/ex-info (core.clj:4739)

REPL | runtimeevaluation / assertion failure or pre/postAssertionErrorN/Aany function"Evaluation error at STACKFN (STACKSRC:LINE), CAUSE(assert false)Evaluation error at user/eval149 (NO_SOURCE_FILE:8), Assert failed: falseAssertionError Assert failed: false  user/eval149 (NO_SOURCE_FILE:8)
REPLprintN/Aallany functionCatch in call to print in clojure.main/replError printing return value at STACKFN (STACKSRC:LINE), encountered: EXCLASS CAUSE ?Error printing return value at some/thing (foo.clj:100). ArithmeticException Divide by 0 


Background info


  • conveys (only) location, but nothing else (no message)
  • if nested (not sure if this is ever a thing), CompilerExceptions do not add value, only want the bottom one as it is most likely to have the correct location
  • CompilerException has fields for source + line but not does store col in a field, just dumps it into the message
  • it's useful for the default message of a CompilerException to include it's cause message and location info in case this is caught by a bit of tooling
    • this is the message being checked in most of the "compile error message" tests in Clojure
  • but the repl can ignore that message completely and build something better

General exception chain form:

  • wrapper chain (usually 0 or 1)
    • always CompilerException, conveys Clojure source location (source/line/col)
    • ignore all but bottom one
    • no message - but message could be dynamic and convey cause info
  • cause chain
    • possibly nested, if nested ignore all but original one (most nested), let's call that the init cause
    • init cause typically has most useful message (something like an NPE may not)
    • init cause stack trace is also the most useful usually - conveys Java and/or Clojure source/line info via stack trace elements
    • init cause may be an ExceptionInfo conveying additional ex-data map

Attributes available when choosing how to display:

  • If compile error, from wrapper:
    • Clojure source file (can be special NO_SOURCE_PATH value)
    • Clojure line
    • Clojure column
  • Init cause:
    • Message
    • Exception info map (if ExceptionInfo)
    • Cause exception class (probably not useful if RuntimeException, Exception, etc). ever relevant?
      • Other common ones are stuff in java.lang - IllegalArg, NPE, IllegalState, UnsupportedOp, Runtime, E
    • Stack trace (top element conveys class name/line)
      • Stack trace element has class name, method name, file name, and line number)
  1. Jul 16, 2018

    It might be helpful to add an additional category.

    Clojure Core Macroexpansion: These core macro exceptions are extremely common, and while they are technically macroexpansion errors, the coding situation/use-case is more similar to a syntax error. In this case, the stacktrace has much less value than the location in user code that triggered it.

    The desired "Printed error" behavior is similar to the desired behavior for Macroexpansion spec errors, I really want to see the location in my code where I caused this to occur.

    1. Jul 16, 2018

      I don't think this is a special case - the same would be of true of your code calling a macro in a library (or in your own code). I've been experimenting with capturing the macro form and printing in this case and it's pretty useful (but you can only go one "level", as macroexpansion is happening in a loop). Even so, it seems quite helpful.

  2. Aug 11, 2018

    Any chance of "wrong arity" being considered into this?

    "clojure.lang.ArityException: Wrong number of args (2) passed to: foo"

    I really miss 

    "clojure.lang.ArityException: Wrong number of args (2) passed to: foo, expected [3]"

    I appreciate that this may be hard with multiarity fns, but still.

    1. Aug 11, 2018

      This page is really about how to categorically throw and print classes of errors, not about any particular error (there are some examples here, but just to serve as examples of a class). So the best way to approach this is to file a jira.

      Functions of course have multiple and possibly variable arity and function objects have no notion of “expected” arity. So I’d say it’s unlikely this is something we would do as described. But stepping back, the problem is - you invoked it wrong, can we give you more feedback to invoke it correctly, and that’s maybe a better problem (this starts to include spec instrument errors too for example).