Namespaces and requires
BasicA namespace is Clojure's unit of modularity. It groups vars (defined
with defclojure.core/defBind a name to a value in the current namespace.view on clojuredocs →, defnclojure.core/defnDefine a named function.view on clojuredocs →, …) under a common qualified name and controls how other
namespaces see them. The file src/myapp/core.clj must begin with
(ns myapp.core ...) — file path and namespace name are mirrored, with -
in namespace names becoming _ on disk (myapp-utils → myapp_utils.clj).
The ns form
(ns myapp.users
(:require [clojure.string :as str]
[clojure.set :refer [union]]
[myapp.db :as db])
(:import [java.time Instant LocalDate]))
(defn touch [u]
(assoc u :seen-at (Instant/now)))Three blocks inside ns:
:requirepulls in Clojure namespaces.:importpulls in Java classes.- (Less common today:
:use,:refer-clojure,:gen-class.)
Inside :require, three modes:
| Form | Effect | When |
|---|---|---|
[clojure.string :as str] | Alias only; access via str/join | Default — keeps origin visible |
[clojure.set :refer [union difference]] | Pull specific names into your ns | Heavily-used operators or isclojure.test/isAssert a single boolean expression inside a deftest.view on clojuredocs →, deftestclojure.test/deftestDefine a unit test.view on clojuredocs → in tests |
[my.lib :refer :all] | Pull everything | Almost never — pollutes your ns |
The pragmatic default is :as aliases. :refer is reserved for short,
canonical names that read better unqualified (<!clojure.core.async/<!Park-take from a channel inside a go block.view on clojuredocs →, >!clojure.core.async/>!Park-put a value onto a channel inside a go block.view on clojuredocs →, isclojure.test/isAssert a single boolean expression inside a deftest.view on clojuredocs →, testingclojure.test/testingGroup assertions under a label.view on clojuredocs →).
Qualified vs unqualified
Every var has a fully-qualified name:
clojure.string/join ;; full name
str/join ;; via the alias above
join ;; only if you `:refer`red itThis matters because keywords can also be qualified. The :: reader
shorthand auto-qualifies a keyword with the current namespace:
;; inside the namespace myapp.users
::id ;; reads as :myapp.users/id
::user/id ;; reads as :myapp.users/id too — if `user` is an aliasPlain :id is unqualified and global. ::id is namespaced — it can't
collide with an :id from another module. Qualified keywords show up later
in tools like [[clojure-spec]] and Datomic, where they make map contracts
unambiguous across libraries.
Loading and reloading at the REPL
Inside a running REPL you don't write ns forms — you require:
(require '[myapp.users :as users])
(require '[myapp.users :as users] :reload) ;; pick up file edits
(require '[myapp.users :as users] :reload-all) ;; reload transitive depsEditors send these for you when you save a file. The reloaded namespace
replaces the previous one — any defclojure.core/defBind a name to a value in the current namespace.view on clojuredocs → you removed from source still
lingers in the live ns until you restart or use tools.namespace's
refresh.
File-to-namespace mapping
| Namespace | File on classpath |
|---|---|
myapp.core | myapp/core.clj |
myapp.users.db | myapp/users/db.clj |
my-app.core | my_app/core.clj (dashes → underscores) |
The classpath is set by your build tool — [[tools-deps]] (deps.edn) or
Leiningen (project.clj) — and the namespace declaration inside the file
must match the path.
ClojureScript and .cljc
.clj→ Clojure (JVM) only..cljs→ ClojureScript only..cljc→ shared. Uses reader conditionals:#?(:clj ... :cljs ...).
Most modern libraries publish their public API as .cljc and dispatch
host-specific code internally.
Try it
Common pitfalls
- Circular requires. A requires B requires A. Restructure or extract a third namespace with shared definitions.
- Side-effects at the top level. A
(def conn (db/connect ...))at ns load time will fire whenever the namespace is required. Use [[component-systems]] for anything stateful. useinstead ofrequire + :refer.useis legacy; everything you bring in becomes a name in your ns, and you lose the origin. Prefer explicit:refer [...]lists.- Forgetting
:as.(:require [very.long.namespace.path])requires you to typevery.long.namespace.path/fooevery time. Alias it.
Real-world
| Pattern | Where |
|---|---|
| One namespace per "thing" (entity, route, service) | Web apps, CRUD systems |
core ns as the public API; helpers in impl/* | Libraries (e.g. clojure.core.async) |
.cljc for shared schema/utility code | Full-stack apps (Clojure backend + CLJS frontend) |
| Qualified keywords per ns | Datomic, [[malli]], [[clojure-spec]] schemas |
tools.namespace/refresh | Reloaded workflow in long-running dev REPLs |
Check yourself
? quiz
You require `[clojure.string :as str]`. Which name reaches `clojure.string/upper-case`?
Exercise
Create two namespaces:
myapp.mathexportingsquareandcube.myapp.mainthat requiresmyapp.mathwith aliasmand uses both functions.
Then sketch the same project with the math functions split into
myapp.math.basic and myapp.math.advanced. Decide: should myapp.main
require both, or should myapp.math re-export them from a single facade
namespace?