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. |
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.
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.
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.
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.
| 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. |
Take an existing Maven module and answer three questions before adding Clojure:
clojure.java.api.Clojure, or through a separate runtime boundary?Those answers matter more than copying a plugin snippet. They determine whether the Maven integration will stay maintainable after the first proof of concept.