Learn how Leiningen uses project.clj, named tasks, profiles, plugins, the project REPL, and uberjar packaging so you can work confidently in existing Clojure repositories.
Leiningen is the Clojure tool Java engineers often understand fastest because it behaves like a project automation tool: one project file, named commands, dependencies, tests, a REPL, and a packaging task.
The important point is not that Leiningen is “the Maven of Clojure.” The better mental model is:
| Java habit | Leiningen equivalent |
|---|---|
Read pom.xml or build.gradle |
Read project.clj |
| Run a named build task | Run lein test, lein run, or lein uberjar |
| Start a project-aware shell | Run lein repl |
| Package an executable artifact | Run lein uberjar |
| Use build profiles | Use Leiningen profiles |
Leiningen is a project automation tool. In a typical app, it owns:
| Concern | Leiningen mechanism |
|---|---|
| Project identity | defproject in project.clj |
| Dependencies | :dependencies |
| Source and test paths | Defaults plus optional configuration |
| REPL startup | lein repl |
| Test execution | lein test |
| Application execution | lein run |
| Packaging | lein uberjar |
| Extensions | Plugins and profiles |
That makes it a good bridge for Java developers joining mature Clojure projects. If a repository has project.clj, begin by learning its lein workflow instead of immediately proposing a tool migration.
project.cljA small Leiningen project file might look like this:
1(defproject orders-service "0.1.0-SNAPSHOT"
2 :description "Order ingestion service"
3 :dependencies [[org.clojure/clojure "1.12.5"]
4 [cheshire "5.13.0"]]
5 :main ^:skip-aot orders-service.core
6 :target-path "target/%s"
7 :profiles {:dev {:dependencies [[nrepl "1.1.0"]]}
8 :uberjar {:aot :all}})
Read the file from the outside in:
| Form or key | Meaning |
|---|---|
defproject |
Project name and version |
:dependencies |
Runtime libraries resolved from Maven-style coordinates |
:main |
Namespace containing -main for lein run and packaging |
^:skip-aot |
Avoids ahead-of-time compilation during normal development |
:profiles |
Environment-specific additions or packaging behavior |
:target-path |
Where generated build output goes |
You do not need every Leiningen option on day one. You need to know which command your team expects and which part of project.clj controls that command.
| Command | Use it when |
|---|---|
lein help |
You need to inspect available tasks |
lein new app my-app |
You want a small application scaffold |
lein repl |
You want a REPL with project dependencies loaded |
lein test |
You want the normal test suite |
lein run |
You want to invoke the configured -main |
lein uberjar |
You want a standalone deployment jar |
For everyday development, lein repl is more important than it first appears. It is not just a console; it is an interactive process with the project classpath, dependencies, and namespaces available.
Leiningen has two extension points you will see in real repositories:
| Extension point | What it does | Java comparison |
|---|---|---|
| Profile | Adds or overrides config for a context | Maven profile or Gradle source set/configuration |
| Plugin | Adds tasks or behavior to Leiningen | Maven plugin or Gradle plugin |
Profiles are often used for :dev, :test, and :uberjar. Plugins are common in older projects for formatting, linting, test runners, deployment, or framework-specific tasks.
Do not blindly copy profiles between projects. A profile changes the classpath and sometimes the runtime behavior.
Use Leiningen directly when:
project.cljlein commandslein uberjarAvoid a tool migration until you can state exactly what problem the migration solves. “The official CLI exists” is not enough by itself.
| Mistake | Result | Better move |
|---|---|---|
Treating lein run as the only workflow |
Slow feedback loop | Use lein repl for interactive development |
Editing project.clj like a static dependency list only |
Broken profiles or packaging | Read task/profile interactions |
| Assuming Leiningen is obsolete | Misreading mature codebases | Learn it well enough to contribute |
| Adding plugins casually | Harder builds and slower startup | Prefer simple project config first |
Migrating to deps.edn before understanding the project |
Churn without value | Match the repository’s current toolchain |
project.clj is the center of a Leiningen project.lein repl is part of the development loop, not just a debugging tool.