How to think about the current Clojure toolchain: Clojure CLI, deps.edn, tools.build, and where Leiningen still fits in existing systems.
Build tools in modern Clojure should no longer be taught as a simple two-way tie between Leiningen and “tools.deps.” The current default story is:
deps.edn for dependency and classpath configurationtools.build when you need reproducible artifact buildsThat distinction matters because older Clojure material often overstates Leiningen as the universal default or describes tools.deps as if it were only a niche alternative. The official docs are now centered on the Clojure CLI toolchain.
The Clojure CLI is the current official entry point for running programs, starting REPLs, resolving dependencies, and invoking build tasks. It is not merely a REPL launcher. It is the normal front door for modern Clojure projects.
With a minimal deps.edn, you can already define a project:
1{:paths ["src" "resources"]
2 :deps {org.clojure/clojure {:mvn/version "1.12.4"}}}
That alone gives you:
For many projects, that is enough to start development.
deps.edn Is About Classpaths and Dependenciesdeps.edn does not try to be every build concern at once. Its core job is to declare:
That smaller scope is a strength. It keeps dependency management explicit and composable instead of bundling every workflow behind one plugin system.
Typical alias usage might separate:
This makes the project easier to reason about than a single giant build configuration that quietly mutates behavior across environments.
tools.build Handles Artifact CreationWhen you need jars, uberjars, generated poms, or repeatable release tasks, the official direction is tools.build.
The mental model matters: a build is a Clojure program, not magic hidden inside a plugin DSL.
1{:paths ["src"]
2 :deps {org.clojure/clojure {:mvn/version "1.12.4"}}
3 :aliases
4 {:build {:deps {io.github.clojure/tools.build {:git/tag "TAG" :git/sha "SHA"}}
5 :ns-default build}}}
Then the project can define a build.clj with explicit tasks. That is powerful because the build itself becomes:
This is the biggest conceptual shift from older “build tool” lessons. Modern Clojure increasingly favors explicit build programs over plugin-heavy hidden behavior.
Leiningen still matters in real codebases.
It remains useful when:
Leiningen should be treated as:
What should change is the teaching default. A new lesson should not present Leiningen as the assumed first tool unless the page is specifically about legacy project maintenance.
The stronger comparison is not “Leiningen or tools.deps?” It is:
deps.edn for dependency and execution workflowtools.build for artifact creation and scripted build logicThat is more accurate than collapsing everything into one binary choice.
Choose the modern CLI stack when:
Stay with Leiningen when:
The wrong move is often not “using Leiningen.” The wrong move is pretending the ecosystem still centers on it as the unquestioned default.
flowchart LR
A["deps.edn"] --> B["Clojure CLI"]
B --> C["REPL / Run / Test"]
B --> D["Build Alias"]
D --> E["tools.build Tasks"]
E --> F["Jar / Uberjar / Publish"]
This is the shape modern Clojure readers should recognize first.
deps.edn.tools.build is the preferred direction for explicit artifact-building workflows.