Browse Clojure Foundations for Java Developers

Namespaces, require, and import

Organize code with ns and require, keep name origins visible, and use imports only where they improve clarity.

Namespaces are where Clojure’s small syntax pays off. They give names context, keep function origins visible, and let you write concise code without scattering imports everywhere.

For a Java engineer, the closest analogy is “packages plus imports,” but that comparison is incomplete. A Clojure namespace is also a live mapping from symbols to vars and classes. That is why namespace hygiene has such a strong effect on readability.

Start With ns

Most Clojure source files begin with a single ns form:

1(ns my.app.orders
2  (:require [clojure.string :as str]
3            [my.app.money :as money])
4  (:import [java.time Instant]))

This does several things at once:

  • sets the current namespace for the file
  • declares other namespaces this file depends on
  • optionally imports Java classes

By convention, a file maps one-to-one with one namespace. That is one reason Clojure codebases are usually easy to navigate once the naming convention clicks.

:require Is The Main Tool

In modern Clojure code, :require does most of the work.

Common patterns:

1[clojure.string :as str]
2[clojure.set :refer [union intersection]]
3[company.application.user :as user]

Use them for different reasons:

  • :as creates a short alias like str/trim
  • :refer brings selected vars into the current namespace
  • plain require without extras just loads the namespace

The safest default is usually :as. It keeps origins visible at the call site:

1(str/lower-case "DEV@EXAMPLE.COM")

That is easier to review than an unqualified lower-case whose origin you must remember.

Use :refer Selectively

:refer is useful, but it should feel intentional.

Good cases:

  • test namespaces that need a few common helpers
  • very common names that improve readability without creating ambiguity
  • DSL-like code where the origin is already obvious

Less good cases:

  • pulling in many names from several namespaces
  • hiding where important functions come from
  • creating collisions that force the reader to inspect the ns form constantly

For Java developers, think of :refer as “import static, but use with restraint.”

Where use Fits Today

You will still see use in older material and some legacy code. It refers public vars into the current namespace automatically. That broad auto-referral is exactly why many teams avoid it in new code: it makes name origin harder to see and increases collision risk.

So the practical guidance is:

  • learn what use does so you can read older code
  • prefer ns with :require and selective :refer in new code

That is a style recommendation for maintainability, not a statement that use has vanished from the language.

Namespace Aliases Make Large Systems Readable

Aliases are one of the most useful everyday tools in Clojure:

1(ns my.app.billing
2  (:require [my.app.money :as money]
3            [my.app.tax :as tax]))
4
5(defn invoice-total [invoice]
6  (-> invoice
7      money/subtotal
8      tax/apply-tax))

The aliases make origins explicit without repeating long fully qualified names.

This is a major part of how Clojure stays readable even though functions are globally named by namespace and var.

:as-alias Helps Qualified Keywords

Current Clojure also supports :as-alias, which creates a namespace alias without loading the namespace:

1(ns my.app.invoice
2  (:require [company.application.user :as-alias user]))
3
4{::user/id 42}

This is especially useful when the alias is mainly there to qualify keywords for data, specs, or destructuring, rather than to call vars from a loadable namespace.

For Java readers, this can feel surprising at first because the alias is helping with the shape of data, not with method calls.

:import Is For Java Classes, Not Clojure Code

If you need Java interop, use :import for classes:

1(ns my.app.clock
2  (:import [java.time Instant ZoneId]))
3
4(defn now []
5  (Instant/now))

You do not use :import for Clojure namespaces. That is what :require is for.

A healthy default is to import Java classes only when it improves readability. Fully qualified class names are also fine if the use is rare and you want to keep the ns form smaller.

How This Differs From Java

The Java comparison is helpful up to a point:

  • namespace name ~= package-like organization
  • :require + :as ~= import plus alias-like shorthand
  • :import ~= Java class imports

But Clojure goes further:

  • aliases are lightweight and local to the namespace
  • vars remain globally reachable by fully qualified names
  • data code and interop code all meet in the same ns form

That unified setup is one reason a good ns form tells you a lot about a file before you even read the functions.

Practical Defaults

If you want a strong default style:

  • start each file with ns
  • prefer :require with :as
  • use :refer narrowly
  • use :import only for Java classes you actually use
  • treat use as something to understand, not as your default

That set of habits will make your code much easier for other Clojure developers to scan.

Quiz: Mastering Clojure Namespaces and require

### What is the primary purpose of namespaces in Clojure? - [x] To organize code and avoid name clashes - [ ] To improve performance - [ ] To enable dynamic typing - [ ] To enforce security > **Explanation:** Namespaces give names context and keep code organized, much like packages, but they are also live mappings from symbols to vars and classes. ### Which form is normally used to declare a namespace at the top of a file? - [x] `ns` - [ ] `require` - [ ] `use` - [ ] `import` > **Explanation:** The `ns` form establishes the file's namespace and is also where most dependency declarations live. ### What is the practical default for bringing another Clojure namespace into a file? - [x] Use `:require`, usually with `:as` - [ ] Use `:import` for the namespace - [ ] Use `use` for every dependency - [ ] Refer every public var without qualification > **Explanation:** `:require` is the main tool for loading Clojure namespaces. `:as` keeps call-site origins visible and is a strong default. ### How do you create a namespace alias in Clojure? - [x] Using the `:as` option with `:require` - [ ] Using the `:alias` keyword - [ ] By renaming the namespace - [ ] By using `use` > **Explanation:** `:as` gives a namespace a shorter local name, such as `str` for `clojure.string`. ### Which mechanism is used to bring Java classes into a Clojure namespace? - [x] `:import` - [ ] `:require` - [ ] `use` - [ ] `def` > **Explanation:** `:import` is for Java classes. Clojure namespaces themselves are brought in with `:require`. ### What is a potential downside of using `use` in Clojure? - [x] It can lead to name clashes and hide origins - [ ] It cannot load code - [ ] It only works with Java classes - [ ] It prevents aliasing completely > **Explanation:** `use` broadly refers public vars into the current namespace, which hides name origins and makes collisions more likely. ### When is `:refer` most appropriate? - [x] When you want a small, intentional set of names in scope - [ ] When you want every public var from every dependency unqualified - [ ] When you need to import Java classes - [ ] When you want to replace `ns` > **Explanation:** `:refer` works best when you bring in only a few carefully chosen names and keep the tradeoff explicit. ### What does `:as-alias` do? - [x] It creates a namespace alias for qualification without loading the namespace - [ ] It imports Java classes under a nickname - [ ] It renames a var inside another namespace - [ ] It refers all public vars automatically > **Explanation:** `:as-alias` is useful when you want a namespace qualifier for keywords, specs, or destructuring but do not need to load vars from that namespace.
Revised on Friday, April 24, 2026