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 CLIdeps.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 ConfigurationLeiningen 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.
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.
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.
| 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. |
Open a Clojure repository and answer these questions before editing code:
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.