Managing External Dependencies from Clojars

How to evaluate, pin, and add Clojars libraries in current Clojure projects without over-relying on older Leiningen-first workflows.

Clojars is the main community repository for Clojure libraries. In practical terms, it is where many of the libraries you actually use in Clojure projects are published and versioned.

The technical challenge is not just “how do I add a dependency?” The better question is “how do I choose, pin, and maintain third-party libraries without turning the project into a fragile pile of transitive assumptions?”

Start with a Current Dependency Model

For new examples, the default teaching model should be the Clojure CLI and deps.edn. Older guides often over-center project.clj, but current official Clojure guidance is built around deps.edn, aliases, and the CLI tools.

That does not mean Leiningen is invalid. It means new baseline guidance should reflect current defaults first, then mention Leiningen as inherited context when relevant.

Finding the Right Library

Before adding a dependency, check more than the library name and the first code sample.

Review:

  • whether the project is maintained
  • how recently it released
  • whether the README is current
  • whether the library supports your Clojure and JDK targets
  • whether it pulls in heavy or surprising transitive dependencies
  • whether the abstraction is actually needed or the standard library is enough

The cheapest dependency is the one you never needed.

Checking Versions

The official Clojure CLI guide shows a useful pattern for discovering available versions:

1clj -X:deps find-versions :lib clojure.java-time/clojure.java-time

That helps you avoid copying stale version numbers from random blog posts or old answers.

Adding a Library with deps.edn

For a CLI-driven project, adding a library usually means declaring it under :deps:

1{:deps {org.clojure/clojure {:mvn/version "1.12.4"}
2        clojure.java-time/clojure.java-time {:mvn/version "1.4.3"}}}

Then the CLI resolves and downloads the dependency when needed.

That model scales better than scattered local setup notes because the dependency graph is explicit and checked into the repo.

Use Aliases for Optional Tooling

Not every dependency belongs in the base runtime classpath. Development-only tooling, formatting tools, benchmarking helpers, and test libraries often fit better in aliases.

1{:paths ["src"]
2 :deps {org.clojure/clojure {:mvn/version "1.12.4"}}
3 :aliases
4 {:test {:extra-paths ["test"]
5         :extra-deps {org.clojure/test.check {:mvn/version "1.1.1"}}}
6  :dev  {:extra-paths ["dev"]}}}

This keeps the main application dependency set narrower and makes intent clearer.

Leiningen Still Matters in Existing Codebases

If you are working in an older project, dependency declarations may still live in project.clj:

1(defproject my-project "0.1.0-SNAPSHOT"
2  :dependencies [[org.clojure/clojure "1.12.4"]
3                 [clojure.java-time "1.4.3"]])

That is fine in an established codebase. The main mistake is teaching it as the only modern way to manage dependencies.

Pin Versions Intentionally

A healthy policy is:

  • pin explicit versions
  • upgrade on purpose
  • review changelogs for important libraries
  • avoid floating or hand-wavy version strategy

This matters because dependency drift is often discovered only during an upgrade, an incident, or a CI rebuild on a fresh machine.

Watch the Transitive Graph

Adding one small library can pull in:

  • a large logging stack
  • old serialization code
  • conflicting JSON libraries
  • outdated HTTP clients
  • multiple overlapping abstractions for the same problem

That is why dependency choice is also architecture choice.

    flowchart TD
	    A["Need a library"] --> B["Evaluate maintenance and fit"]
	    B --> C["Check versions and docs"]
	    C --> D["Add explicit dependency"]
	    D --> E["Inspect transitive graph and runtime impact"]
	    E --> F{"Still worth it?"}
	    F -- Yes --> G["Pin and document"]
	    F -- No --> H["Choose another library or avoid dependency"]

The important decision point is not after production incidents. It is before the dependency becomes part of the project’s long-term surface area.

Common Dependency Management Mistakes

  • adding a library because a tutorial used it, without checking whether it is still maintained
  • teaching project.clj as the default for every new example
  • failing to pin versions
  • ignoring the transitive graph
  • using runtime dependencies where a tool alias would be cleaner
  • treating Clojars discovery as a popularity contest instead of a fitness decision

Key Takeaways

  • Clojars is the main community repository for Clojure libraries, but choosing a dependency is an engineering decision, not just a package-install step.
  • For new examples, prefer deps.edn and the Clojure CLI as the default model.
  • Use aliases for optional tooling and test-only dependencies.
  • Pin versions intentionally and inspect the transitive graph.
  • Mention Leiningen when it is relevant to an existing codebase, but do not teach it as the only current path.

References and Further Reading

Ready to Test Your Knowledge?

Loading quiz…
Revised on Thursday, April 23, 2026