Add external Java libraries through deps.edn or Leiningen, then isolate their clients, builders, callbacks, and exceptions behind a small Clojure-facing API.
External Java libraries are often the fastest way to get production capability in a Clojure system: database drivers, cloud SDKs, HTTP clients, observability agents, and file parsers are all on the table.
The Clojure design work begins after the dependency is on the classpath. Decide which namespace owns the Java library, what plain data enters and leaves that boundary, and how library-specific exceptions are translated.
Before diving into the specifics of adding Java libraries, it’s important to understand how dependency management works in Clojure. Clojure projects typically use either Leiningen or tools.deps to manage dependencies. Both tools allow you to specify the libraries your project depends on, automatically downloading and including them in your project’s classpath.
project.clj)Leiningen is a build automation tool for Clojure that uses a project.clj file to define project settings, including dependencies. Here’s a basic structure of a project.clj file:
1(defproject my-clojure-project "0.1.0-SNAPSHOT"
2 :description "A simple Clojure project"
3 :dependencies [[org.clojure/clojure "1.10.3"]
4 [org.some/library "1.2.3"]])
:dependencies: This vector contains the list of dependencies, each specified as a vector with the library’s group ID, artifact ID, and version.deps.edn)tools.deps is a more recent addition to the Clojure ecosystem, providing a flexible way to manage dependencies using a deps.edn file. Here’s an example of a deps.edn file:
1{:deps {org.clojure/clojure {:mvn/version "1.10.3"}
2 org.some/library {:mvn/version "1.2.3"}}}
:deps: This map contains the dependencies, with each key being the library’s coordinate and the value specifying the version.Now that we understand the basics of dependency management, let’s explore how to add external Java libraries to your Clojure project using both Leiningen and tools.deps.
To add a Java library using Leiningen, you simply need to include it in the :dependencies vector in your project.clj file. For example, let’s add the popular Apache Commons Lang library:
1(defproject my-clojure-project "0.1.0-SNAPSHOT"
2 :description "A simple Clojure project with Apache Commons Lang"
3 :dependencies [[org.clojure/clojure "1.10.3"]
4 [org.apache.commons/commons-lang3 "3.12.0"]])
Once you’ve added the dependency, run lein deps to download and include it in your project’s classpath.
With tools.deps, you add a Java library by specifying it in the :deps map in your deps.edn file. Here’s how you can add Apache Commons Lang:
1{:deps {org.clojure/clojure {:mvn/version "1.10.3"}
2 org.apache.commons/commons-lang3 {:mvn/version "3.12.0"}}}
After updating your deps.edn file, use the clj command to resolve and download the dependencies:
1clj -Stree
This command will show the dependency tree, confirming that the library has been added successfully.
Once you’ve added a Java library to your project, you can call its code directly from Clojure. Let’s explore how to do this with some examples.
Suppose you want to use the StringUtils class from Apache Commons Lang to check if a string is empty. Here’s how you can do it in Clojure:
1(ns my-clojure-project.core
2 (:import [org.apache.commons.lang3 StringUtils]))
3
4(defn is-empty? [s]
5 (StringUtils/isEmpty s))
6
7;; Usage
8(println (is-empty? "")) ; true
9(println (is-empty? "Clojure")) ; false
:import: This directive is used to import Java classes into your Clojure namespace.StringUtils/isEmpty: Calls the static isEmpty method from the StringUtils class.Google Guava is another popular Java library that provides utilities for collections, caching, and more. Let’s use Guava’s Joiner class to join a list of strings:
1(ns my-clojure-project.core
2 (:import [com.google.common.base Joiner]))
3
4(defn join-strings [separator strings]
5 (-> (Joiner/on separator)
6 (.join strings)))
7
8;; Usage
9(println (join-strings ", " ["Clojure" "Java" "Scala"])) ; "Clojure, Java, Scala"
Joiner/on: Creates a Joiner instance with the specified separator..join: Joins the strings using the Joiner instance.To highlight the differences and similarities between Java and Clojure, let’s compare the above examples with their Java counterparts.
1import org.apache.commons.lang3.StringUtils;
2
3public class Example {
4 public static void main(String[] args) {
5 System.out.println(StringUtils.isEmpty("")); // true
6 System.out.println(StringUtils.isEmpty("Clojure")); // false
7 }
8}
1import com.google.common.base.Joiner;
2
3public class Example {
4 public static void main(String[] args) {
5 Joiner joiner = Joiner.on(", ");
6 System.out.println(joiner.join("Clojure", "Java", "Scala")); // "Clojure, Java, Scala"
7 }
8}
To deepen your understanding, try modifying the examples above:
To better understand how dependencies are managed in Clojure, let’s visualize the process using a diagram.
graph TD;
A[Define Dependencies] --> B[Leiningen: project.clj];
A --> C[tools.deps: deps.edn];
B --> D[Download Dependencies];
C --> D;
D --> E[Classpath Setup];
E --> F[Use Java Libraries in Clojure];
Diagram Description: This flowchart illustrates the process of adding external Java libraries to a Clojure project. It starts with defining dependencies in either project.clj or deps.edn, followed by downloading the dependencies and setting up the classpath, allowing you to use Java libraries in your Clojure code.
For more information on dependency management and Java interoperability in Clojure, consider exploring the following resources:
deps.edn file, such as specifying a local path or a Git repository for dependencies.Now that we’ve explored how to add external Java libraries to your Clojure projects, you’re well-equipped to enhance your applications with powerful Java tools and libraries.