Browse Learn Clojure Foundations as a Java Developer

Add Clojure to Gradle Builds

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.

Minimal Gradle Shape

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.

Layout and Namespaces

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.

Dependency Configurations Matter

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.

Testing Through Gradle

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?”

When Gradle Is the Wrong Host

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.

Practice

Before introducing Clojure into a Gradle monorepo, write down:

  1. Which plugin version owns Clojure source sets.
  2. Which configurations hold production, test, and REPL-only dependencies.
  3. Which Gradle task CI runs for Clojure tests.
  4. Whether application startup uses clojure.main, gen-class, or a Java adapter.
  5. Which subproject owns the Java/Clojure boundary.

If those answers are clear, Gradle can host Clojure without turning the build into a puzzle.

Knowledge Check

### Why should the Clojure Gradle plugin version be pinned? - [x] Plugin behavior is part of the build contract and pre-1.0 Clojurephant versions can introduce breaking changes - [ ] Gradle refuses to run unpinned plugins - [ ] Clojure cannot load dependencies from Maven Central without a plugin pin - [ ] Pinning changes Clojure syntax > **Explanation:** The plugin controls source sets, tasks, and test behavior. Treat it like build infrastructure, not a casual library. ### What is the benefit of launching `clojure.main` from Gradle during early adoption? - [x] It lets the application run a Clojure namespace without forcing an early generated Java class boundary - [ ] It removes the need for `org.clojure:clojure` - [ ] It turns Clojure code into Groovy - [ ] It makes Gradle ignore the runtime classpath > **Explanation:** `clojure.main -m some.namespace` keeps startup Clojure-native while still letting Gradle own the command. ### What should `./gradlew test` prove in a mixed Java/Clojure project? - [x] Clojure test failures are visible to CI through Gradle's normal test path - [ ] Tests can only be run from a REPL - [ ] Every Clojure namespace has been AOT-compiled - [ ] Java tests are disabled while Clojure tests run > **Explanation:** Reproducible CI behavior matters more than editor-only success. Gradle should fail the build when Clojure tests fail.
Revised on Saturday, May 23, 2026