How the current Clojure toolchain handles dependencies, aliases, classpaths, and where Leiningen still fits in existing codebases.
Dependency management with Leiningen and deps.edn is one of the places where old Clojure advice goes stale fastest. The current official default is the Clojure CLI with deps.edn. Leiningen is still real, still useful, and still common in established codebases, but it is no longer the best starting assumption for new examples.
That distinction matters because dependency management is not just about downloading jars. It shapes how a team runs code, isolates environments, builds artifacts, debugs classpath problems, and explains the project to the next engineer.
deps.ednThe Clojure CLI is now the center of the official toolchain. It uses deps.edn for dependency coordinates, paths, aliases, and execution modes. That makes it the simplest default for new work because the mental model is direct:
:deps declares libraries:paths declares source roots:aliases describe alternate classpaths and execution contexts-M, -X, and -T decide how code is runA minimal project can stay very small:
1{:paths ["src"]
2 :deps {org.clojure/clojure {:mvn/version "1.12.0"}}
3 :aliases
4 {:dev {:extra-paths ["dev"]}
5 :test {:extra-paths ["test"]}}}
That simplicity is the real advantage. You can explain the project layout, execution flow, and dependency graph without first teaching a large build abstraction.
Leiningen is not obsolete. It remains common in mature projects, published libraries, and teams that already have a working project.clj ecosystem. It still gives you a cohesive model for:
An established Leiningen application can still be a perfectly healthy codebase:
1(defproject my-app "0.1.0-SNAPSHOT"
2 :dependencies [[org.clojure/clojure "1.12.0"]]
3 :profiles {:dev {:resource-paths ["dev-resources"]}
4 :test {:test-paths ["test"]}})
The practical rule is:
deps.edn by default for new examples and new projectsThat is a better rule than pretending one tool has erased the other.
New Clojure users often think deps.edn is just a different syntax for listing dependencies. It is more than that. It is also the basis for how the CLI computes classpaths and execution contexts.
That means aliases are central:
1{:paths ["src"]
2 :deps {org.clojure/clojure {:mvn/version "1.12.0"}}
3 :aliases
4 {:dev {:extra-paths ["dev"]}
5 :test {:extra-paths ["test"]}
6 :build {:ns-default build}}}
With the current CLI, you then choose execution style explicitly:
clj for a REPLclojure -M to run a main namespace or scriptclojure -X to call a function with keyword argumentsclojure -T to run an installed or aliased toolThat explicitness is one of the biggest differences from older tutorials.
The most dangerous dependency bug is not “a library failed to download.” It is “the classpath resolved, but not the way you thought.”
So the real skill is inspection:
clojure -X:deps tree to inspect the dependency tree in current CLI workflowsclj -Stree, which still exists in many environmentsFor Leiningen, lein deps :tree still plays the same diagnostic role.
The core lesson is the same in both worlds: when versions clash, inspect the actual graph before “fixing” anything.
tools.build Is Related, But SeparateAnother source of stale guidance is mixing dependency declaration and artifact-building into one mental bucket. In modern Clojure, they are related but distinct:
deps.edn is about dependencies, classpaths, paths, and aliasestools.build is about scripted artifact creation and release tasksThat separation is healthy. It keeps the dependency model simple while still allowing explicit build logic when you need jars, uberjars, generated poms, or release automation.
If a tutorial teaches deps.edn as though it should replace every build concern automatically, it is teaching the wrong abstraction boundary.
Use deps.edn when:
-M, -X, and -TStay with Leiningen when:
project.cljThis is an engineering decision, not a purity test.
flowchart LR
A["Dependency Coordinates"] --> B["deps.edn or project.clj"]
B --> C["Classpath Resolution"]
C --> D["Execution Mode or Build Task"]
D --> E["REPL, App Run, Tool Run, or Artifact Build"]
The important thing to notice is that dependency management is upstream of almost everything else. If the classpath model is muddy, the rest of the project feels muddy too.
deps.edn is only a prettier dependency filedeps.edn.tools.build as a complementary build tool, not a replacement for dependency declaration.