Browse Learn Clojure Foundations as a Java Developer

Read Clojure Macros as Code Transformations

Learn the basic macro mental model: Clojure code is data, macros receive unevaluated forms, and macro expansion produces ordinary Clojure that Java engineers can review like generated source.

A Clojure macro is a function-like definition that receives unevaluated forms and returns a new form. That returned form is what Clojure evaluates.

The important phrase is unevaluated forms. A function receives values after its arguments run. A macro receives the code before those arguments run.

Code Is Data

Clojure source is made from the same data structures you already use: lists, vectors, maps, symbols, strings, numbers, and keywords.

1'(+ 1 2)
2;; => (+ 1 2)
3
4(type '(+ 1 2))
5;; => clojure.lang.PersistentList

The quoted expression is a list whose first element is the symbol +. Because code is represented as data, a macro can build or rewrite that data before evaluation.

Function Call vs Macro Call

Question Practical difference
When do arguments evaluate? Function arguments evaluate before the call; macro arguments evaluate only where the expansion places them.
What does it receive? A function receives runtime values; a macro receives source forms represented as data.
What does it return? A function returns a runtime value; a macro returns code to compile or evaluate.
How do you debug it? Debug functions through values and stack traces; debug macros by inspecting expansion first.

This difference is why macros can create syntax such as when, ->, and, or, let, and doseq.

A Small Macro

Start with something familiar:

1(defmacro my-when
2  [condition & body]
3  `(if ~condition
4     (do ~@body)))

This macro does not “run the body.” It returns code that may run the body later. Expand it:

 1(macroexpand-1
 2  '(my-when (pos? total)
 3     (println "positive")
 4     :ok))
 5
 6;; roughly:
 7(if (pos? total)
 8  (do
 9    (println "positive")
10    :ok))

Read the expansion as if a teammate generated that code in a pull request. If the expansion is hard to explain, the macro is already too clever.

Java Comparison

Macros are not Java reflection. Reflection discovers and invokes members at runtime. Macros rewrite source forms before ordinary evaluation.

Java-adjacent idea Clojure macro distinction
Annotation processor Both can generate code before runtime, but a Clojure macro is called directly in Clojure source.
Reflection Reflection is runtime inspection; macro expansion is compile/load-time transformation.
Lombok-style boilerplate removal Similar motivation, but macro expansion is visible with macroexpand-1.
Builder pattern Usually still a function/data problem unless syntax or evaluation control matters.

Review Checklist

Before accepting a macro, verify:

Check Good sign
Expansion readability The generated code is ordinary and reviewable.
Evaluation control The macro has a reason a function cannot satisfy.
Argument evaluation Each caller expression runs the intended number of times.
Name hygiene Temporary names cannot capture or collide with caller names.
Tests Tests cover both normal results and at least one expansion shape.

Knowledge Check

### What does a Clojure macro receive from the caller? - [x] Unevaluated source forms represented as data. - [ ] Only runtime values after all arguments execute. - [ ] Java bytecode instructions. - [ ] A reflection object for each argument. > **Explanation:** Macros run before normal evaluation and receive forms such as lists, symbols, vectors, and maps. That is what lets them control whether and where caller expressions evaluate. ### Why should Java engineers inspect macro expansion? - [x] The expansion is the generated code that will actually run. - [ ] Macro expansion shows JVM garbage collection pauses. - [ ] Macro expansion automatically proves the macro is thread-safe. - [ ] Macro expansion replaces the need for tests. > **Explanation:** `macroexpand-1` and `macroexpand` show the generated Clojure code. Review that code before trusting the macro abstraction. ### Which problem usually does not require a macro? - [x] Transforming already evaluated data. - [ ] Preventing an argument from evaluating. - [ ] Introducing a binding form. - [ ] Building a small syntax shape around repeated boilerplate. > **Explanation:** If ordinary values are enough, use a function. Macros earn their complexity when evaluation or syntax must be controlled.
Revised on Saturday, May 23, 2026