Organize `src`, `test`, and `resources` directories around classpath roots, mirrored test namespaces, and the differences from Maven or Gradle source sets.
Java projects often separate code with Maven or Gradle source sets such as src/main/java and src/test/java. Clojure projects usually use a flatter layout: src/ for production namespaces, test/ for test namespaces, and resources/ for files that should be available on the JVM classpath.
The important idea is not the folder name itself. The important idea is the classpath root. A namespace path is resolved relative to one of the roots your tool puts on the classpath.
| Directory | Role |
|---|---|
src/ |
Production Clojure namespaces, similar to Java’s src/main/java source role |
test/ |
Test namespaces, usually added for test runs rather than production packaging |
resources/ |
Classpath files such as EDN defaults, logging config, templates, and static assets |
dev/ |
REPL helper namespaces and local workflow code added only by dev aliases or profiles |
Suppose the production namespace is order-service.pricing. Its file should live under a source root:
1(ns order-service.pricing)
2
3(defn subtotal [order]
4 (reduce + (map :price (:items order))))
5
6(defn total [order]
7 (* (subtotal order) 1.13))
The path is:
1src/order_service/pricing.clj
Notice again that order-service becomes order_service on disk.
Tests normally mirror the production namespace and add -test to the namespace name. That becomes _test in the file name.
Path:
1test/order_service/pricing_test.clj
File:
1(ns order-service.pricing-test
2 (:require [clojure.test :refer [deftest is testing]]
3 [order-service.pricing :as pricing]))
4
5(deftest subtotal-adds-item-prices
6 (testing "subtotal is a pure calculation over order data"
7 (is (= 20
8 (pricing/subtotal {:items [{:price 12} {:price 8}]})))))
This style gives you the same fast navigation benefit as Java test packages: if you know the production namespace, you can predict the test namespace.
With the Clojure CLI and deps.edn, :paths identifies normal classpath roots. Test and development roots are commonly added through aliases so production runs do not accidentally depend on test code.
1{:paths ["src" "resources"]
2 :deps {org.clojure/clojure {:mvn/version "1.12.5"}}
3 :aliases
4 {:dev {:extra-paths ["dev" "test"]}
5 :test {:extra-paths ["test"]}}}
Leiningen projects use project.clj instead, but the mental model is similar: production source, test source, resources, and development-only additions are separate inputs to the JVM classpath.
| Java source-set idea | Clojure equivalent |
|---|---|
Production source under src/main/java |
Production namespaces under src |
Test source under src/test/java |
Test namespaces under test |
Resources under src/main/resources |
Classpath resources under resources |
| Package maps to directories | Namespace maps to directories and file names |
| JUnit, TestNG, or AssertJ | clojure.test or library-specific runners |
Clojure’s flatter layout can look too informal at first. In practice, the discipline comes from namespaces, aliases, and classpath roots rather than nested Java source-set folders.
Keep files in resources/ when code should load them through the classpath. Examples include default EDN configuration, migration templates, logging configuration, and small static assets used by the application.
1(ns order-service.config
2 (:require [clojure.edn :as edn]
3 [clojure.java.io :as io]))
4
5(defn read-defaults []
6 (some-> "defaults.edn"
7 io/resource
8 slurp
9 edn/read-string))
The Java analogy is ClassLoader#getResource, not reading a relative file from the current working directory. That difference matters in packaged applications, Docker images, tests, and CI.
Before you call a Clojure project “organized,” check these points:
| Check | What to look for |
|---|---|
| Source path | Namespace path is predictable under src/ and matches the file name. |
| Test path | Test namespace mirrors the production namespace rather than living in a flat miscellaneous folder. |
| Resources | Runtime files are loaded from resources/ instead of the shell working directory. |
| Dev helpers | REPL-only code is under dev/ or a dev alias, not required by production startup. |
| Classpath config | deps.edn aliases or Lein profiles explain which roots are active in each command. |
Take one Java package you know well and sketch a Clojure equivalent:
*-test namespaces.resources/.