Browse Learn Clojure Foundations as a Java Developer

Set Up Clojure Alongside a Java Project

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.

Choose An Integration Shape

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.

Establish A Minimal Project Contract

A migration-ready setup should answer these questions:

  • Which build command runs all Java and Clojure tests?
  • Where do Clojure source and test files live?
  • How are dependencies pinned and reviewed?
  • How does Java call the first Clojure function?
  • How does Clojure call existing Java code?
  • What command opens a REPL with the same classpath CI uses?
  • What artifact is deployed?

If developers have to answer these differently on every machine, the setup is not ready.

Keep The Boundary Smoke Test Small

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.

Configure CI For Migration Risk

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.

Make The REPL Useful To Java Developers

The REPL should load the real project classpath, not a disconnected sandbox. A migration REPL is useful when a developer can:

  • require the migrated namespace
  • construct realistic input data
  • call Java classes already on the project classpath
  • run the same pure functions tested in CI
  • inspect exceptions with meaningful data
  • avoid mutating production-like resources accidentally

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.

Avoid Setup Drift

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.

Practice

  1. Add a tiny Clojure namespace that transforms one string or map.
  2. Add a Java smoke test that invokes the namespace.
  3. Add the Clojure test command to the required CI path.
  4. Open a REPL with the project classpath and run the same function against realistic fixture data.

Key Takeaways

  • Migration setup should integrate with the existing JVM build and CI workflow.
  • Prove Java-to-Clojure invocation with a tiny smoke test before migrating real logic.
  • Keep the REPL connected to the real project classpath.
  • Make dependency review, tests, and fixture comparison required, not optional.
  • Avoid version-specific setup snippets unless they are owned and maintained by the project.

Quiz: Clojure Environment Setup

### What should the first setup prove? - [x] Java and Clojure can build, test, and call across the boundary. - [ ] The team can delete the Java build. - [ ] The REPL replaces CI. - [ ] Every service must become standalone. > **Explanation:** A migration environment must prove the production-relevant boundary, not just a standalone Clojure demo. ### Why add a boundary smoke test? - [x] It verifies namespace loading, classpath, packaging, and Java-to-Clojure invocation. - [ ] It proves all business logic is correct. - [ ] It removes the need for Clojure tests. - [ ] It replaces dependency review. > **Explanation:** A small smoke test catches setup failures before real migration logic depends on the path. ### What should a useful migration REPL include? - [x] The real project classpath. - [ ] Only a scratch namespace disconnected from the project. - [ ] Production credentials by default. - [ ] No access to Java classes. > **Explanation:** The REPL is useful when it can exercise the same code and classpath used by the project. ### Which CI check is specific to migration safety? - [x] Fixture comparison between old Java and new Clojure behavior. - [ ] Formatting only. - [ ] A screenshot of the repository. - [ ] A manual note that tests were skipped. > **Explanation:** Fixture comparison proves behavior is preserved while implementation changes. ### What is setup drift? - [x] Local, CI, and production paths disagree about classpath, commands, or dependencies. - [ ] Clojure running on the JVM. - [ ] Java calling Clojure through an adapter. - [ ] A documented test command. > **Explanation:** Drift makes migration failures hard to reproduce and support.
Revised on Saturday, May 23, 2026