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

  1. Make the catch binding a simple unqualified symbol: (catch Exception e ...)
  2. If you want destructuring, bind a symbol then destructure inside the body
  3. 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

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


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)