Configure Clojure in a JVM codebase with repeatable builds, dependency boundaries, REPL workflow, CI checks, and Java interop smoke tests instead of treating setup as a standalone toy project.
Environment setup for a Java-to-Clojure migration is not just installing a REPL. The useful setup lets Java and Clojure code build together, test together, and fail visibly in CI before the migrated code reaches production.
Java teams usually already have Maven, Gradle, CI, logging, artifact publishing, and deployment conventions. Clojure should fit into that JVM workflow unless there is a clear reason to split it out.
The first setup decision is where Clojure lives relative to the Java project.
| Shape | Use when | Watch for |
|---|---|---|
| Clojure namespace inside existing JVM module | You are migrating one service or package and Java will keep calling it. | Build tool must compile/package Clojure predictably. |
| Separate Clojure module in a multi-module build | You want a clearer boundary and artifact ownership. | Dependency versions and release order need discipline. |
| Standalone Clojure service | The boundary is already network-based or operationally separate. | You have created service, deployment, and observability overhead. |
| Experiment outside the repo | You are learning or prototyping only. | Prototype code must not become an untested production path. |
For most first migrations, an in-repo module or namespace is enough. Start with the smallest shape that can run tests and prove Java-Clojure interop.
A migration-ready setup should answer these questions:
If developers have to answer these differently on every machine, the setup is not ready.
Add one trivial function and one Java call path before migrating real logic. The goal is to prove classpath, namespace loading, packaging, and test execution.
1(ns migration.smoke)
2
3(defn normalize-status [status]
4 (-> status str clojure.string/lower-case keyword))
Java can call that function through the Clojure runtime API or through a small adapter owned by the build.
1import clojure.java.api.Clojure;
2import clojure.lang.IFn;
3
4public final class ClojureSmoke {
5 private static final IFn REQUIRE = Clojure.var("clojure.core", "require");
6
7 public static Object normalizeStatus(String status) {
8 REQUIRE.invoke(Clojure.read("migration.smoke"));
9 IFn normalize = Clojure.var("migration.smoke", "normalize-status");
10 return normalize.invoke(status);
11 }
12}
This is not the final adapter design. It is a smoke test. Once it passes in CI, the team knows the runtime path is real.
CI should do more than compile. It should protect the Java-Clojure boundary.
| CI check | What it catches |
|---|---|
| Java tests | Existing behavior and adapter callers still work. |
| Clojure tests | Pure functions and Clojure shells behave as expected. |
| Boundary smoke test | Namespace loading and Java-to-Clojure invocation work in the packaged environment. |
| Dependency scan or review | New JVM dependencies do not silently conflict with existing ones. |
| Fixture comparison | Old Java and new Clojure implementations return equivalent results. |
Keep the first CI path simple. Add sophistication only after the team can run the same checks locally.
The REPL should load the real project classpath, not a disconnected sandbox. A migration REPL is useful when a developer can:
The REPL is a development tool, not a substitute for tests. Anything discovered in the REPL that matters should become a fixture, unit test, or integration test.
| Drift symptom | Fix |
|---|---|
| Local REPL classpath differs from CI | Document one command or alias for the project REPL. |
| Java tests pass but Clojure tests are optional | Put both in the required build path. |
| Clojure dependency versions are changed ad hoc | Review dependency changes like any other JVM dependency. |
| Prototype namespace becomes production code | Move it under the real source tree and add tests before use. |
| Build command depends on one developer’s machine | Use repository scripts or CI-visible commands. |
Environment setup is complete only when a new developer can clone the repository, run the documented commands, and see the same boundary checks as CI.