Organize Clojure code with ns, require aliases, selective refer, Java imports, and namespace hygiene that keeps symbol origins clear for Java engineers.
Namespaces are where Clojure’s small syntax becomes maintainable at project scale. They give symbols context, make dependencies visible, and let a file stay concise without hiding where important functions come from.
For a Java engineer, the closest analogy is “package plus imports,” but a Clojure namespace is more active than a Java package declaration. It is a live mapping from symbols to Vars and classes, and that mapping affects how every form in the file is read.
ns FormMost source files begin with one ns form:
1(ns my.app.orders
2 (:require [clojure.string :as str]
3 [my.app.money :as money]
4 [my.app.tax :as tax])
5 (:import [java.time Instant]))
That form establishes the current namespace and declares the code this namespace depends on. A good ns form works like a table of contents for the file.
| Part | Purpose | Java comparison |
|---|---|---|
my.app.orders |
Current namespace | Package-like location |
:require |
Clojure namespace dependencies | Imports plus aliases |
:as str |
Local alias | Short qualified name |
:import |
Java class imports | Java import statements |
By convention, one source file usually maps to one namespace. The namespace my.app.orders normally lives at src/my/app/orders.clj.
:require With :asThe most common dependency pattern is an alias:
1(ns my.app.billing
2 (:require [clojure.string :as str]
3 [my.app.money :as money]))
4
5(defn normalized-email [email]
6 (some-> email str/trim str/lower-case))
Aliases keep origins visible at the call site:
1str/lower-case
2money/format-cents
That is easier to review than unqualified names whose origins must be remembered from the top of the file.
:refer Deliberately:refer brings selected Vars into the current namespace:
1(ns my.app.report-test
2 (:require [clojure.test :refer [deftest is testing]]
3 [my.app.report :as report]))
That is reasonable in tests because names like deftest, is, and testing are part of the testing vocabulary. It is less helpful when business functions lose their origin:
1;; Harder to review when many namespaces refer unqualified names.
2(calculate-total order)
Default to aliases in production namespaces. Use :refer only when the unqualified name genuinely improves readability.
use as Legacy StyleOlder Clojure material may use use:
1(use 'clojure.set)
The problem is that use broadly refers public Vars into the current namespace, making origins harder to see and collisions easier to create. Modern code usually prefers explicit :require forms:
1(ns my.app.permissions
2 (:require [clojure.set :as set]))
3
4(set/intersection user-permissions required-permissions)
Learn use so you can read older code. Do not make it your default style.
Use :import for Java classes, not Clojure namespaces:
1(ns my.app.clock
2 (:import [java.time Instant ZoneId]))
3
4(defn now []
5 (Instant/now))
For rare uses, a fully qualified class name can be clearer than adding another import:
1(java.util.UUID/randomUUID)
The same rule applies as in Java: import when it improves readability; avoid imports that make the top of the file noisy.
:as-aliasClojure also uses namespace names to qualify keywords:
1{:user/id 42
2 :user/email "dev@example.com"}
Sometimes you want an alias for data qualification even when you do not need to load Vars from that namespace:
1(ns my.app.invoice
2 (:require [my.app.user :as-alias user]))
3
4{::user/id 42}
For Java readers, this is unusual: the alias is helping describe data shape, not method access. The payoff is that map keys can carry stable, collision-resistant meaning.
| Decision | Strong default | Why it helps |
|---|---|---|
| Clojure dependency | :require ... :as alias |
Keeps origins visible |
| Test vocabulary | selective :refer |
Reduces noise where names are standard |
| Java class | :import |
Keeps interop concise |
| Rare Java class use | fully qualified class | Avoids import clutter |
| Legacy broad import | avoid use |
Prevents hidden origins and collisions |
| Data-only alias | :as-alias |
Supports qualified keywords without loading Vars |
| Mistake | Better habit |
|---|---|
Importing Clojure namespaces with :import |
Use :require |
| Referring many business functions unqualified | Use aliases so origins stay visible |
| Treating namespace names as arbitrary labels | Match namespace and file path consistently |
| Hiding Java interop everywhere | Import only classes that clarify call sites |
| Ignoring qualified keywords | Use them when map keys cross namespace boundaries |
use into :require with an alias.ns form with :import.money/format-cents or referred unqualified.:require with :as for production code.:refer narrowly, especially for test vocabulary or DSL-like contexts.:import only for Java classes.ns form makes the rest of the file easier to scan.