Build Tools in Modern Clojure

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:

  • the Clojure CLI as the main command-line entry point
  • deps.edn for dependency and classpath configuration
  • tools.build when you need reproducible artifact builds
  • Leiningen mainly as an important legacy and still-usable tool in existing codebases

That 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.

Start with the Clojure CLI

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:

  • a classpath definition
  • dependency resolution
  • a consistent command-line entry point

For many projects, that is enough to start development.

deps.edn Is About Classpaths and Dependencies

deps.edn does not try to be every build concern at once. Its core job is to declare:

  • source paths
  • dependencies
  • aliases for different tasks or environments

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:

  • dev tools
  • test execution
  • build tasks
  • profiling tools

This makes the project easier to reason about than a single giant build configuration that quietly mutates behavior across environments.

tools.build Handles Artifact Creation

When 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:

  • reviewable
  • testable
  • scriptable
  • easy to evolve with the project

This is the biggest conceptual shift from older “build tool” lessons. Modern Clojure increasingly favors explicit build programs over plugin-heavy hidden behavior.

Where Leiningen Still Fits

Leiningen still matters in real codebases.

It remains useful when:

  • you are maintaining an existing Leiningen project
  • the team already knows and trusts the workflow
  • the project depends on plugins or release flow that would not be worth migrating immediately

Leiningen should be treated as:

  • valid
  • still present in the ecosystem
  • often inherited rather than chosen fresh

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 Better Comparison

The stronger comparison is not “Leiningen or tools.deps?” It is:

  • Clojure CLI + deps.edn for dependency and execution workflow
  • tools.build for artifact creation and scripted build logic
  • Leiningen for older codebases or teams that deliberately keep its workflow

That is more accurate than collapsing everything into one binary choice.

How to Choose

Choose the modern CLI stack when:

  • starting a new project
  • teaching current Clojure practice
  • building with explicit classpaths and scripted build tasks
  • you want the official toolchain path

Stay with Leiningen when:

  • the project is already stable and productive on Leiningen
  • migration would create churn without real payoff
  • the release workflow depends on established plugins or conventions

The wrong move is often not “using Leiningen.” The wrong move is pretending the ecosystem still centers on it as the unquestioned default.

A Current Workflow Model

    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.

Key Takeaways

  • The current official default starts with the Clojure CLI and deps.edn.
  • tools.build is the preferred direction for explicit artifact-building workflows.
  • Leiningen still matters, especially in established projects, but it is no longer the best teaching default for new work.
  • Build guidance should distinguish dependency/classpath management from artifact packaging.
  • Modern Clojure build lessons should reflect the official toolchain, not just historical popularity.

References and Further Reading

Ready to Test Your Knowledge?

Loading quiz…
Revised on Thursday, April 23, 2026