Integrate Clojure into Gradle with explicit plugin choice, source-set ownership, dependency scopes, test wiring, and runtime boundaries for mixed JVM projects.
Gradle can be a good host for Clojure when a Java organization already depends on Gradle features such as multi-project builds, version catalogs, convention plugins, dependency locking, or shared CI tasks. The risk is flexibility without discipline. A Gradle build can hide classpath and source-set behavior in several places, so Clojure integration should be explicit.
The modern Gradle-specific path is Clojurephant, whose Clojure plugin is published as dev.clojurephant.clojure. It is useful for Gradle-native projects, but it is still pre-1.0, so pin the plugin version and read release notes before upgrades.
| Gradle concern | Clojure-specific decision |
|---|---|
| Plugin | Use a current Clojure plugin such as Clojurephant when Gradle should own Clojure source sets. |
| Runtime | Declare org.clojure:clojure in the appropriate Gradle configuration. |
| Source layout | Keep Clojure in src/main/clojure and tests in src/test/clojure. |
| Tests | Decide how clojure.test results enter Gradle’s test task and reports. |
| Application run | Decide whether Gradle launches clojure.main, a generated class, or a Java adapter. |
For a mixed Java/Clojure project, start with a build that makes the plugin, repositories, dependencies, and source-set convention visible.
1plugins {
2 id 'application'
3 id 'dev.clojurephant.clojure' version '0.9.1'
4}
5
6repositories {
7 mavenCentral()
8}
9
10dependencies {
11 implementation 'org.clojure:clojure:1.12.5'
12 testRuntimeOnly 'dev.clojurephant:jovial:0.4.3'
13}
14
15application {
16 mainClass = 'clojure.main'
17}
18
19tasks.named('run') {
20 args '-m', 'order-service.main'
21}
22
23tasks.withType(Test).configureEach {
24 useJUnitPlatform()
25}
This example keeps the runtime entry point in Clojure by launching clojure.main with -m order-service.main. That is usually simpler than forcing an early Java class shape with gen-class.
Clojurephant follows a Gradle source-set layout that Java engineers can recognize.
1settings.gradle
2build.gradle
3src/
4 main/
5 clojure/order_service/main.clj
6 java/com/example/orders/JavaApi.java
7 resources/defaults.edn
8 test/
9 clojure/order_service/main_test.clj
10 java/
11 resources/
12 dev/
13 clojure/user.clj
The Clojure namespace still follows normal Clojure naming rules:
1(ns order-service.main)
2
3(defn -main [& _args]
4 (println "Order service started"))
The folder is Gradle-shaped; the namespace is Clojure-shaped. Keep those two ideas separate when debugging.
Gradle dependency buckets decide where a library is visible. Java teams already know this from implementation, runtimeOnly, and testImplementation; the same discipline applies to Clojure.
| Configuration | Use it for |
|---|---|
implementation |
Clojure runtime and libraries needed by production code. |
runtimeOnly |
Runtime-only JVM libraries, logging backends, or drivers. |
testImplementation |
Test libraries needed to compile or load test code. |
testRuntimeOnly |
Test engines or runners needed only when executing tests. |
Avoid putting every Clojure library in a broad configuration just to make the build pass. If a dependency only belongs to tests or the REPL, keep it out of the production runtime classpath.
Clojure’s built-in test library is clojure.test, but Gradle’s standard reporting expects the JVM test infrastructure. Clojurephant commonly pairs with a JUnit Platform engine such as Jovial so Clojure tests can run through Gradle’s test task.
1(ns order-service.main-test
2 (:require [clojure.test :refer [deftest is]]
3 [order-service.main :as main]))
4
5(deftest smoke-test
6 (is (fn? main/-main)))
The key question for CI is not “can I run a test from my editor?” The key question is “does ./gradlew test fail when a Clojure test fails, and do reports show the failure?”
Gradle is powerful, but it is not automatically the simplest Clojure build. Prefer a separate Clojure module or service when the Gradle integration becomes mostly glue code.
| Warning sign | Better move |
|---|---|
| Clojure is used only for a standalone service | Use Clojure CLI, Leiningen, or a separate Gradle subproject instead of mixing source roots. |
| Build logic hides source sets in several convention plugins | Create one documented convention plugin or keep Clojure separate. |
| Java callers force object-heavy APIs | Put a narrow adapter at the boundary and keep Clojure internals data/function-oriented. |
| Tests pass only through an IDE REPL | Make Gradle’s test task the reproducible CI path. |
Before introducing Clojure into a Gradle monorepo, write down:
clojure.main, gen-class, or a Java adapter.If those answers are clear, Gradle can host Clojure without turning the build into a puzzle.