deps.edn is a configuration file used by the Clojure CLI (command-line interface) to manage project dependencies, classpath definitions, and build parameters. Written in EDN (Extensible Data Notation), a subset of Clojure data structures, it specifies library coordinates, local paths, and execution aliases. Unlike older build tools like Leiningen or Boot, deps.edn emphasizes simplicity and direct integration with Clojure's language features, making it the primary tool for dependency management in modern Clojure projects.
1 Overview and Purpose
1.1 Role in Clojure Ecosystem
The deps.edn file serves as the central configuration artifact for Clojure projects that use the official Clojure CLI. It declares external library dependencies, source code paths, and runtime configurations in a purely declarative data format. This approach shifts the paradigm from imperative build scripts to data-driven project definitions, allowing the CLI to compute a precise classpath deterministically.
1.2 Relation to Clojure CLI
The deps.edn file is consumed directly by the clj and clojure commands provided by the Clojure CLI toolchain. Upon invocation, the CLI reads the deps.edn file from the current working directory (and optionally from the user's home directory for global configuration). It uses the tools.deps.alpha library to resolve dependencies, fetch artifacts from Maven repositories or Git sources, compute the full Java classpath, and then launch the Clojure runtime.
1.3 Comparison with Leiningen and Boot
| Feature | deps.edn | Leiningen (project.clj) | Boot (build.boot) | |
|---|---|---|---|---|
| Configuration style | Declarative data (EDN) | Declarative DSL (Clojure) | Imperative code (Clojure) | |
| Plugin system | Composable aliases | Leiningen plugins | Boot tasks/pods | |
| Dependency resolution | tools.deps.alpha (Maven/Git/local) | Leiningen's built-in resolver | Maven Aether | |
| Learning curve | Low (pure data) | Moderate | Steeper (code-based) |
The key philosophical difference is that deps.edn treats configuration as inert data rather than executable code, simplifying reasoning about project structure and enabling better tooling support.
2 File Format and Syntax
2.1 EDN Basics
EDN (Extensible Data Notation) is a subset of Clojure's reader syntax. A deps.edn file is a single EDN map. Common EDN elements used in deps.edn include:
- Keywords:
:deps,:paths,:aliases - Symbols:
org.clojure/clojure(used as keys in dependency maps) - Strings:
"src","1.11.1" - Vectors:
["src" "resources"] - Maps:
{:mvn/version "1.11.1"} - Tagged literals:
#profile {:test {:extra-paths ["test"]}}
2.2 Standard Keys
The root deps.edn map supports several standard keys, each with a specific purpose.
2.2.1 :deps
The :deps key holds a map of dependency specifications. Each key is a fully qualified symbol representing a library coordinate (e.g., org.clojure/clojure). The corresponding value is a map containing version information and optional modifiers. Common keys within this map include:
:mvn/version— specifies a Maven version string.:git/url— specifies a Git repository URL.:local/root— specifies a path to a local project.:exclusions— a vector of library coordinates to exclude from transitive resolution.
2.2.2 :paths
The :paths key is a vector of directory paths (relative to the project root) that will be included in the Java classpath. If not specified, the default value is ["src"]. Resources, configuration files, or compiled classes are often added via this key.
2.2.3 :aliases
The :aliases key is a map where each key is an alias name (a keyword) and each value is a configuration map. When the alias is activated on the command line (e.g., clojure -M:test), its configuration map is merged with the base configuration. The alias map can contain any of the standard deps.edn keys.
2.2.3.1 :extra-deps
Dependencies that are only applied when the alias is active. Commonly used for test frameworks (e.g., lambdaisland/kaocha), development tools (e.g., nrepl/nrepl), or build utilities (e.g., seancorfield/depstar).
2.2.3.2 :extra-paths
Additional classpath directories active only under the alias. The most common use case is adding "test" to the classpath for running tests.
2.2.3.3 :main-opts
Arguments passed to the -main function when using the -M execution mode. This is typically used to specify the main namespace and any command-line arguments for application entry points.
2.2.4 :mvn/repos
The :mvn/repos key is a map of repository name keywords to repository URL strings. It allows users to define custom Maven repositories (internal corporate repositories, snapshot repositories, or alternative mirrors). The default Maven Central repository is always available unless explicitly overridden.
3 Dependency Specification
3.1 Maven Coordinates
Maven coordinates are the standard way to specify dependencies from Maven repositories. The format is:
group-id/artifact-id {:mvn/version "1.0.0"}
For example:
org.clojure/clojure {:mvn/version "1.11.1"}
com.google.guava/guava {:mvn/version "32.1.2-jre"}
The version string follows Maven versioning conventions and can include version ranges, SNAPSHOT indicators, or release markers.
3.2 Git Coordinates
Git coordinates allow direct inclusion of dependencies from Git repositories. They are specified using the :git/url key, and must include at least one of :sha, :tag, or :branch to pin a specific version. The Clojure CLI will clone the repository, compute a classpath, and generate a dependency version based on the commit hash.
com.example/git-lib {:git/url "https://github.com/example/git-lib.git"
:sha "abc123def456"}
Using :tag provides a human-readable reference:
com.example/git-lib {:git/url "https://github.com/example/git-lib.git"
:tag "v1.0.0"}
3.3 Local Dependencies
Local dependencies point to other projects on the local filesystem using the :local/root key. The project at that path must itself contain a valid deps.edn file. The CLI reads the local project's configuration and merges its dependencies and paths into the main classpath.
com.example/local-lib {:local/root "../local-lib"}
This is particularly useful for monorepo development and cross-project testing.
3.4 Version Ranges and Overrides
Maven version ranges are supported in deps.edn and follow standard Maven syntax:
[1.0.0,2.0.0)— any version from 1.0.0 to less than 2.0.0(,1.5.0]— any version less than or equal to 1.5.0
Dependency overrides are specified using the :override-deps key within an alias. This forces specific versions of transitive dependencies, helping to resolve conflicts:
:aliases {:resolve-conflicts {:override-deps {com.example/problem-lib {:mvn/version "2.0.0"}}}}
4 Project Structure and Classpath
4.1 Default Source Paths
When no :paths key is specified in deps.edn, the CLI defaults to including only the "src" directory in the classpath. This encourages a conventional project layout where source code resides in src/ and tests reside in test/ (the latter typically added via an alias).
4.2 Custom Path Configuration
Projects can customize their classpath by explicitly defining :paths. Common additions include:
"resources"— for configuration files, static assets, or other runtime files."target/classes"— for compiled Java classes."dev-resources"— for development-only resources.
The order of paths in the vector determines their precedence on the classpath.
4.3 Classpath Calculation
The CLI computes the final classpath through a multi-step process:
- Read the base deps.edn (project level).
- Resolve all direct and transitive dependencies using Maven/Git/local coordinates.
- Apply any active aliases (merging their configuration into the base).
- Combine all specified paths (base paths + extra paths from aliases).
- Add the resolved jar files to the classpath.
- Check for a
checkoutsdirectory and override any matching jars. - Cache the computed classpath for reuse in subsequent invocations.
5 Aliases and Profiles
5.1 Defining Aliases
Aliases are defined in the :aliases map at the root level of deps.edn. Each alias is a keyword associated with a configuration map that can contain any of the standard deps.edn keys. Active aliases are specified on the command line using the -A flag (or implicitly via -M, -X, or -T modes).
:aliases {:dev {:extra-paths ["dev"]
:extra-deps {nrepl/nrepl {:mvn/version "1.0.0"}}}
:test {:extra-paths ["test"]
:extra-deps {lambdaisland/kaocha {:mvn/version "1.78.0"}}}}
5.2 Common Use Cases
5.2.1 Testing Aliases
Testing aliases add test-specific dependencies and paths. A typical test alias includes a test runner and the test source directory:
:aliases {:test {:extra-paths ["test"]
:extra-deps {lambdaisland/kaocha {:mvn/version "1.78.0"}}
:main-opts ["-m" "kaocha.runner"]}}
5.2.2 Development Tools (Repl, REPL)
Development aliases configure tools that aid in interactive development, such as REPL clients, code hot-reloaders, or structural editors:
:aliases {:dev {:extra-paths ["dev"]
:extra-deps {nrepl/nrepl {:mvn/version "1.0.0"}
cider/piggieback {:mvn/version "0.5.3"}}
:main-opts ["-m" "nrepl.cmdline"]}}
5.2.3 Build Aliases
Build aliases encapsulate tasks such as compiling, packaging, or deploying. These often use dedicated libraries like depstar for building uberjars:
:aliases {:uberjar {:extra-deps {com.github.seancorfield/depstar {:mvn/version "2.1.303"}}
:main-opts ["-m" "clj-new.tools.build"]
:exec-fn clj-new.tools.build/uberjar}}
5.3 Merging and Resolution
When multiple aliases are activated (e.g., clojure -M:dev:test), the CLI merges their configurations using a deep merge strategy:
- Maps: merged recursively (later aliases override earlier ones for duplicate keys).
- Vectors: concatenated (order preserved).
- Scalars: later values override earlier values.
This composability allows users to combine independent configurations without conflicts.
6 Execution and Commands
6.1 Using clj and clojure
The Clojure CLI provides two main executables: clj and clojure. The clj command is a wrapper around clojure that adds readline support (via rlwrap) for a better interactive REPL experience. Both commands accept similar arguments and read deps.edn from the current working directory.
6.1.1 -Sdeps Flag
The -Sdeps flag allows users to specify an alternative deps configuration by providing a string of EDN data that gets merged with the project's deps.edn:
clojure -Sdeps '{:deps {nrepl/nrepl {:mvn/version "1.0.0"}}}' -M -m nrepl.cmdline
This is useful for ad-hoc dependencies or temporary configurations.
6.1.2 -M and -X Flags
The -M flag runs a -main function specified by :main-opts in an alias or directly on the command line:
clojure -M:test
The -X flag invokes a Clojure function directly with a hash-map of arguments, using the :exec-fn key from an alias:
clojure -X:build uberjar
This mode encourages a functional approach to build scripts, where build steps are just Clojure functions.
6.2 Running Tests
The typical command to run tests is:
clojure -M:test
The :test alias normally specifies the test runner as the main entry point. For example, using Kaocha:
clojure -M:test
The exact command depends on the alias configuration; some projects use different test runners like cognitect/test-runner.
6.3 Launching a REPL
To launch a REPL with the full project classpath:
clj
For a customized REPL environment with development tools:
clojure -M:dev:repl
This activates the :dev and :repl aliases, adding their extra dependencies and paths before entering the REPL.
7 Advanced Topics
7.1 Coordinated Projects (Monorepos)
For monorepo setups or projects with multiple interdependent libraries, deps.edn supports coordinated development through two features:
:local/root: allows a project to depend on another local project by referencing its directory. The CLI reads the local project's deps.edn and merges its classpath.checkoutsdirectory: a top-level directory namedcheckoutscan contain symbolic links to local projects. The CLI automatically finds these and overrides any resolved jar dependencies, enabling seamless cross-project testing without modifying deps.edn.
7.2 Integration with Build Tools (tools.deps, deps.clj)
The tools.deps.alpha library is the underlying dependency resolver used by the official CLI. It is also available as a standalone library for use in custom build tools. Third-party alternatives like deps.clj provide polyglot compatibility (e.g., for scripting languages that need to invoke Clojure). These tools all parse the same deps.edn format, ensuring interoperability.
7.3 Lockfile and Reproducible Builds
The CLI can generate a lockfile (deps.edn.lock) that records the exact resolved versions of all transitive dependencies. This lockfile can be committed to version control, ensuring that every build uses the same dependency versions regardless of when it runs or on which machine. To generate or update the lockfile:
clojure -X:deps lock
Once a lockfile is present, the CLI uses it instead of resolving dependencies from scratch, guaranteeing reproducible builds.
8 Practical Examples
8.1 Minimal deps.edn for a Library
{:deps {org.clojure/clojure {:mvn/version "1.11.1"}}}
This minimal configuration includes only the Clojure compiler and core libraries, defaulting to the src source path.
8.2 Full Project with Aliases
{:deps {org.clojure/clojure {:mvn/version "1.11.1"}
com.google.guava/guava {:mvn/version "32.1.2-jre"}}
:paths ["src" "resources"]
:mvn/repos {:central {:url "https://repo1.maven.org/maven2"}
:clojars {:url "https://repo.clojars.org"}}
:aliases {:test {:extra-paths ["test"]
:extra-deps {lambdaisland/kaocha {:mvn/version "1.78.0"}}
:main-opts ["-m" "kaocha.runner"]}
:dev {:extra-paths ["dev"]
:extra-deps {nrepl/nrepl {:mvn/version "1.0.0"}}}
:build {:extra-deps {com.github.seancorfield/depstar {:mvn/version "2.1.303"}}
:exec-fn hf.depstar/uberjar}}}
8.3 Managing Multiple Environments
Projects often need distinct configurations for development, staging, and production. This is achieved through composable aliases:
:aliases {:base {:paths ["config"]}
:dev {:extra-deps [dev-only-deps...]}
:prod {:extra-paths ["config/prod"]}
:staging {:extra-paths ["config/staging"]}}
Activating :dev and :base together gives a development environment, while :prod and :base gives a production environment.
9 Troubleshooting and Common Pitfalls
9.1 Dependency Conflicts
When multiple dependencies require different versions of the same library, the CLI resolves by default to the highest version. If conflicts cause errors (e.g., incompatible bytecode or missing methods), users can:
- Use
:override-depsin an alias to force a specific version. - Inspect the dependency tree with
clojure -Stree(or-Sforest). - Examine the resolved classpath with
clojure -Spath.
9.2 Missing Repositories
Errors indicating unavailability of artifacts usually point to missing repository configurations. Common causes include:
- Forgetting to add Clojars (
https://repo.clojars.org) when using non-Maven-Central libraries. - Using a corporate repository without configuring it in
:mvn/repos. - Network connectivity issues or incorrect repository URLs.
9.3 Path Resolution Errors
Classpath errors often stem from incorrect path specifications:
- The
flycheck-modeis an Emacs term; applying similar logic: path errors manifest as class-not-found exceptions or missing resources. - Ensure
:pathsentries are relative and that directories actually exist. - Verify that
:local/rootpaths point to valid projects with their own deps.edn.
10 Future Directions and Community Adoption
The deps.edn format and the Clojure CLI have gained widespread adoption within the Clojure community, particularly among new projects and those requiring reproducible builds. The Clojure core team continues to develop tools.deps.alpha, with ongoing improvements to:
- Lockfile reproducibility and performance.
- Integration with modern CI/CD pipelines.
- Support for GraalVM native-image compilation.
- Enhanced aliasing and profile composition mechanisms.
The ecosystem has largely converged on deps.edn as the standard for dependency management, with major libraries, frameworks, and tools providing first-class support for the format. While Leiningen remains in use for legacy projects, deps.edn represents the present and future of Clojure project configuration.