Browse Learn Clojure Foundations as a Java Developer

Read Clojure Project Config Files

Read `deps.edn`, `project.clj`, aliases, and profiles through a Java build-tool lens so you know which file controls dependencies, classpaths, tasks, and local workflow.

A Clojure repository usually has one file that tells the tool what belongs on the classpath and which tasks or aliases are available. Modern projects often use deps.edn with the Clojure CLI. Older or Leiningen-centered projects use project.clj. Some repositories contain both during migration, but one should clearly own CI and release behavior.

For Java engineers, the key is to separate three concerns that Maven or Gradle often hide behind one build file:

Concern Where to look in Clojure
Dependencies :deps in deps.edn, or :dependencies in project.clj
Classpath roots :paths and alias :extra-paths, or Leiningen source, test, resource, and profile settings
Tasks Clojure CLI aliases, :main-opts, :exec-fn, build namespaces, Lein tasks, or plugins
Environment variants Clojure CLI aliases such as :dev, :test, and :build, or Leiningen :profiles

deps.edn: Data for the Clojure CLI

deps.edn is EDN data read by the Clojure CLI and tools.deps. It describes dependency coordinates, source paths, and aliases that modify how a command runs. It does not define a Maven-like lifecycle by itself; projects commonly add aliases or a build namespace when they need test, build, or release tasks.

1{:paths ["src" "resources"]
2 :deps {org.clojure/clojure {:mvn/version "1.12.5"}}
3 :aliases
4 {:dev {:extra-paths ["dev" "test"]}
5  :test {:extra-paths ["test"]}
6  :run {:main-opts ["-m" "order-service.main"]}}}

Read it from the top down:

Key What to inspect when debugging
:paths Normal classpath roots such as src, resources, or generated source roots
:deps Library coordinates and versions available to normal runs
:aliases Named run modifications used by CI, tests, REPL commands, and build commands
:extra-paths Additional roots that explain test or dev namespaces loading locally but not elsewhere
:main-opts Arguments passed to clojure.main, especially whether -m points to the intended namespace

This file is intentionally declarative. If you are coming from Gradle, do not expect arbitrary build logic inline in deps.edn.

project.clj: Leiningen Project Configuration

Leiningen reads project.clj, which is a Clojure form using defproject. It combines project metadata, dependencies, plugins, profiles, and common tasks.

1(defproject order-service "0.1.0-SNAPSHOT"
2  :description "Order pricing service"
3  :dependencies [[org.clojure/clojure "1.12.5"]]
4  :main order-service.main
5  :profiles
6  {:dev {:source-paths ["dev"]
7         :resource-paths ["resources"]}})

For a Java developer, project.clj feels closer to a compact Maven POM plus common task configuration. The syntax is Clojure, but the job is familiar: identify the project, choose dependencies, choose a main namespace, and define variants.

User-Level Profiles and Local Overrides

Leiningen can also read user-level profile files such as profiles.clj. Treat those as local development overrides, not as source-of-truth project behavior. If CI or another developer needs the setting, it belongs in committed project configuration, not only in a personal profile.

The same principle applies to Clojure CLI user config. Local aliases are useful for personal tools, but team-critical aliases should be committed in the repository.

Choosing the File to Trust

When a repository contains more than one config file, do not guess. Inspect the commands in CI, README, Makefile, Dockerfile, or deployment scripts.

If you see… Confirm by checking…
clojure -M:test or clj -M:dev Clojure CLI aliases in deps.edn and the CI command
lein test or lein uberjar project.clj, :profiles, plugins, and Leiningen task names
bb test or bb build Babashka task definitions in bb.edn
make test Makefile targets that wrap one of the Clojure tools

This is the same habit you already use in Java when a repository has pom.xml, build.gradle, a Dockerfile, and custom shell scripts. The build file matters, but the command that CI actually runs matters more.

Debugging Config Problems

Symptom What to check
Namespace loads in REPL but not CI CI may be missing the alias, profile, or :extra-paths that your local command uses.
Dependency works locally but not for teammates The dependency may exist only in user config or local profiles.clj, not committed project config.
Main namespace cannot start :main-opts or :main may name the wrong namespace.
Tests cannot see source code The active paths may be missing src, test, or the profile that adds them.

Practice

Open a Clojure repository and answer these questions before editing code:

  1. Which command does CI use for tests?
  2. Which file owns dependency versions?
  3. Which directories are classpath roots for production?
  4. Which alias or profile adds test code?
  5. Which namespace is the application entry point?

If you can answer those five questions, the project config is no longer mysterious. It is just a smaller JVM build system with different vocabulary.

Knowledge Check

### What does `:paths` control in a typical `deps.edn` file? - [x] The normal directories placed on the classpath - [ ] The HTTP routes in the application - [ ] The Java version used by the JVM - [ ] The names of all functions in the project > **Explanation:** `:paths` identifies classpath roots such as `src` and `resources`. Namespace files are resolved relative to those roots. ### A repository has both `deps.edn` and `project.clj`. What is the safest first step? - [x] Check CI, README, or wrapper scripts to see which tool is actually used - [ ] Delete one file immediately - [ ] Assume `deps.edn` always overrides Leiningen - [ ] Assume `project.clj` always overrides the Clojure CLI > **Explanation:** Mixed repositories exist during migration or for compatibility. The command that runs tests and releases tells you which configuration is authoritative. ### Why should team-critical aliases or profiles be committed to the repository? - [x] CI and teammates need the same classpath and task behavior - [ ] Clojure cannot read user-level configuration - [ ] Leiningen ignores personal profiles - [ ] It makes every alias run in production automatically > **Explanation:** Personal configuration is fine for local convenience, but shared behavior must be reproducible in CI and on other machines.
Revised on Saturday, May 23, 2026