Browse Learn Clojure Foundations as a Java Developer

Add Clojure to Maven Builds

Use Maven as the owning build for mixed Java and Clojure modules by declaring the Clojure runtime, source roots, plugin responsibilities, and compile/test boundaries explicitly.

Maven can own a Clojure build when the repository, CI pipeline, release process, and deployment tooling are already Maven-centered. The goal is not to make Clojure feel like Java. The goal is to make the classpath, source roots, tests, and packaging predictable enough that Java and Clojure teams can share the same module without mystery.

Use Maven for Clojure when the build system is already part of the organizational contract. If you are starting a standalone Clojure service, the Clojure CLI or Leiningen is often simpler.

Maven concern Clojure-specific decision
Runtime dependency Add org.clojure:clojure explicitly so the runtime version is reviewable.
Source roots Keep Clojure under src/main/clojure and tests under src/test/clojure.
Compilation Decide whether Maven should AOT-compile namespaces or only check/load them.
Testing Wire clojure.test into Maven’s lifecycle or run Clojure tests through an explicit goal/script.
Packaging Confirm whether source files, compiled classes, or both belong in the final artifact.

Add the Clojure Runtime

In a Maven-owned module, Clojure is still just a JVM dependency. Declare it in pom.xml so dependency review and version mediation work the same way they do for Java libraries.

1<dependencies>
2  <dependency>
3    <groupId>org.clojure</groupId>
4    <artifactId>clojure</artifactId>
5    <version>1.12.5</version>
6  </dependency>
7</dependencies>

That dependency brings in the Clojure runtime and core transitive libraries. It does not, by itself, teach Maven where your .clj files live or how tests should run.

Use a Maven Plugin Deliberately

The common Maven integration path is com.theoryinpractise:clojure-maven-plugin. It provides goals such as clojure:compile, clojure:test, clojure:run, and REPL-oriented goals. A minimal Maven-owned Clojure module usually makes the plugin explicit instead of relying on tribal knowledge.

 1<build>
 2  <plugins>
 3    <plugin>
 4      <groupId>com.theoryinpractise</groupId>
 5      <artifactId>clojure-maven-plugin</artifactId>
 6      <version>1.9.3</version>
 7      <extensions>true</extensions>
 8      <configuration>
 9        <sourceDirectories>
10          <sourceDirectory>src/main/clojure</sourceDirectory>
11        </sourceDirectories>
12        <testSourceDirectories>
13          <testSourceDirectory>src/test/clojure</testSourceDirectory>
14        </testSourceDirectories>
15      </configuration>
16    </plugin>
17  </plugins>
18</build>

The important part for a Java engineer is not the XML volume. It is ownership. If Maven owns the module, Maven must know about Clojure source roots, test source roots, and the phase where Clojure code is checked or compiled.

Keep the Layout Predictable

Use Maven’s familiar source-set shape, but let namespaces control the Clojure file paths inside the Clojure roots.

1order-service/
2  pom.xml
3  src/main/java/com/example/orders/JavaApi.java
4  src/main/clojure/order_service/pricing.clj
5  src/test/clojure/order_service/pricing_test.clj
6  src/main/resources/defaults.edn

The namespace in src/main/clojure/order_service/pricing.clj should be:

1(ns order-service.pricing)
2
3(defn subtotal [order]
4  (reduce + (map :price (:items order))))

The Maven directory gives Java developers the source-set boundary they expect. The namespace-to-path rule still follows Clojure conventions inside that boundary.

Avoid Accidental AOT Coupling

Ahead-of-time (AOT) compilation produces Java class files for Clojure namespaces. It is useful when Java code must call generated classes directly, when a gen-class entry point is required, or when packaging demands compiled classes. It can be a liability during early adoption because it makes reload behavior, class generation, and Java-facing APIs more rigid.

Situation Reasonable Maven choice
Java calls a narrow Clojure adapter AOT only that adapter namespace and keep most Clojure pure/data-oriented.
Clojure code is run by clojure.main Prefer source loading or selective compile checks before broad AOT.
CI needs fast failure on invalid namespaces Use plugin compile/check behavior without designing everything around generated classes.
Packaging an executable Java application Decide explicitly whether the final artifact needs Clojure source, classes, or both.

Do not AOT every namespace just because the build has a compile phase. AOT is an integration tool, not a general substitute for good namespace design.

Debug Maven/Clojure Failures

Symptom First check
Maven cannot find org.clojure:clojure Repository configuration, corporate proxy settings, and dependency management overrides.
Clojure namespace cannot be located Source directory configuration and namespace-to-file path conversion.
Java cannot call a Clojure function Whether the function is exposed through an adapter, generated class, or explicit runtime invocation.
Tests pass in the REPL but fail in Maven Maven test classpath, plugin test source roots, and whether the same fixtures are loaded.

Practice

Take an existing Maven module and answer three questions before adding Clojure:

  1. Which Maven phase should fail if a Clojure namespace is invalid?
  2. Will Java call Clojure through generated classes, through clojure.java.api.Clojure, or through a separate runtime boundary?
  3. Should the packaged artifact include Clojure source files, AOT classes, or both?

Those answers matter more than copying a plugin snippet. They determine whether the Maven integration will stay maintainable after the first proof of concept.

Knowledge Check

### What does adding `org.clojure:clojure` to `pom.xml` provide? - [x] The Clojure runtime dependency for the module - [ ] Automatic discovery of every `.clj` source root - [ ] A generated Java class for every function - [ ] A replacement for Maven's dependency mechanism > **Explanation:** The dependency adds the Clojure runtime. Source roots, compilation, tests, and packaging still need explicit build configuration. ### Why should AOT compilation be selective in a mixed Java/Clojure Maven module? - [x] It creates Java-facing class artifacts and can make reload and API boundaries more rigid - [ ] It prevents Maven from resolving dependencies - [ ] It disables all Clojure interop - [ ] It is required for every namespace that contains a function > **Explanation:** AOT is useful at Java-facing boundaries, but broad AOT can over-couple Clojure namespace design to generated classes too early. ### A namespace loads in the REPL but Maven cannot find it. What should you inspect first? - [x] Maven's configured Clojure source roots and the namespace file path - [ ] Whether the namespace has a matching Java interface - [ ] Whether the function names are camelCase - [ ] Whether the repository uses Gradle > **Explanation:** REPL and Maven commands may use different classpaths. Start with source roots and namespace-to-file mapping before changing code.
Revised on Saturday, May 23, 2026