clojure/clojure · error · java.lang.IllegalArgumentException
Bad binding form, expected symbol, got:
Error message
Bad binding form, expected symbol, got:
What it means
Sentinel validation in the catch clause parser: the binding position (third element of the catch form) must be a Symbol to receive the caught exception, but another form (string, keyword, vector, etc.) was supplied. At-fault input: a non-symbol binding, e.g. (catch Exception "e" ...).
Solutions
- Make the catch binding a simple unqualified symbol: (catch Exception e ...)
- If you want destructuring, bind a symbol then destructure inside the body
- Check macro-generated code emits a plain symbol
Example fix
;; before
(catch Exception {:keys [message]} ...)
;; after
(catch Exception e (let [{:keys [message]} (ex-data e)] ...)) Defensive patterns
Strategy: validation
Validate before calling
(defn valid-catch? [clause]
(and (seq? clause)
(= 'catch (first clause))
(symbol? (nth clause 2 nil))
(nil? (namespace (nth clause 2 nil))))) Try / catch
(try (eval clause)
(catch IllegalArgumentException e
(when (.startsWith (.getMessage e) "Bad binding form")
(println "catch binding must be a plain symbol")))) Prevention
- Always bind catch to a simple unqualified symbol
- Destructure inside the catch body, not in the binding
- Review macro-generated catch clauses for non-symbol bindings
When it happens
Trigger: (catch Exception "e" ...), (catch Exception [e] ...), or (catch Exception a/b ...) with a namespace-qualified symbol as the binding name.
Common situations: Copy-paste errors in catch clauses; macro-generated catch with a form instead of a symbol; accidentally using destructuring directly in catch (not supported directly).
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Cannot recur across try
- finally clause must be last in try expression
- fn params must be Symbols
- Malformed member expression, expecting (. target member ...)
- Only catch or finally clause can follow catch in try…
AI-assisted analysis of clojure/clojure@f3b143341d (2026-09-09).
Data as JSON: /api/errors/87e19d96f7c5ab49.
Report an issue: GitHub.
Appendix: source
Thrown at src/jvm/clojure/lang/Compiler.java:2834
body = body.cons(f);
}
else
{
if(bodyExpr == null)
try {
Var.pushThreadBindings(RT.map(NO_RECUR, true, METHOD_RETURN_CONTEXT, null));
bodyExpr = (new BodyExpr.Parser()).parse(context, RT.seq(body));
} finally {
Var.popThreadBindings();
}
if(Util.equals(op, CATCH))
{
Class c = HostExpr.maybeClass(RT.second(f), false);
if(c == null)
throw new IllegalArgumentException("Unable to resolve classname: " + RT.second(f));
if(!(RT.third(f) instanceof Symbol))
throw new IllegalArgumentException(
"Bad binding form, expected symbol, got: " + RT.third(f));
Symbol sym = (Symbol) RT.third(f);
if(sym.getNamespace() != null)
throw Util.runtimeException("Can't bind qualified name:" + sym);
IPersistentMap dynamicBindings = RT.map(LOCAL_ENV, LOCAL_ENV.deref(),
NEXT_LOCAL_NUM, NEXT_LOCAL_NUM.deref(),
IN_CATCH_FINALLY, RT.T);
try
{
Var.pushThreadBindings(dynamicBindings);
LocalBinding lb = registerLocal(sym,
(Symbol) (RT.second(f) instanceof Symbol ? RT.second(f)
: null),
null,false);
Expr handler = (new BodyExpr.Parser()).parse(C.EXPRESSION, RT.next(RT.next(RT.next(f))));
catches = catches.cons(new CatchClause(c, lb, handler));
}View on GitHub (pinned to f3b143341d)