Browse Learn Clojure Foundations as a Java Developer

Map Clojure Namespaces to Files

Map Clojure namespaces to source files, require aliases, and Java package instincts so namespace loading errors become easy to diagnose.

A Clojure namespace is the unit you normally load, require, reload, and alias. It plays part of the role a Java package plays, but it is not a class container. A namespace can hold functions, vars, macros, specs, constants, and interop helpers without wrapping them in a class.

The practical rule is simple: a namespace name must line up with the file path that can be found on the classpath.

Clojure namespace Expected file path
orders.core src/orders/core.clj
orders.http.api src/orders/http/
api.clj
order-service.core src/order_service/
core.clj
orders.core-test test/orders/
core_test.clj

The hyphen-to-underscore rule is the part Java engineers usually miss. Use hyphens in namespace names when they read well, but store those namespace segments with underscores in file names.

The ns Form Sets the Loading Boundary

Put the namespace declaration at the top of the file. It tells Clojure which namespace receives the definitions in that file and which other namespaces should be available by alias.

1(ns order-service.pricing
2  (:require [clojure.string :as str]))
3
4(defn normalize-code [code]
5  (-> code
6      str/trim
7      str/upper-case))

For this namespace, the normal production file is:

1src/order_service/pricing.clj

Think of :require as closer to Java’s import plus classpath loading. The alias does not copy definitions into the current namespace. It gives you a short, explicit qualifier such as str/trim.

How Loading Finds the File

When Clojure sees (require 'order-service.pricing), it looks for a loadable resource that matches the namespace convention. The diagram below shows the path translation that matters during debugging.

    flowchart TD
	    A["Namespace symbol"] --> B["order-service.pricing"]
	    B --> C["Dots become directories"]
	    C --> D["Hyphens become underscores"]
	    D --> E["Classpath resource"]
	    E --> F["order_service/pricing.clj"]

What to notice: Clojure does not scan every file looking for a matching (ns ...) form. It uses the namespace name to derive a resource path on the classpath. If the path is wrong, require fails before your function definitions matter.

Java Package Instincts That Transfer

Your Java package-navigation habit is useful, but it needs two adjustments.

Java habit Clojure adjustment
Package name mirrors directories Namespace segments mirror directories, but hyphenated segments use underscores on disk
One public class anchors the file One namespace anchors the file and can contain many public vars
Imports make names shorter :require aliases keep names short without hiding ownership
Package cycles are a design smell Namespace cycles are also a smell and can make REPL reloads painful

The biggest design shift is that a namespace should usually describe a coherent set of functions and data transformations, not a single class. For example, order-service.pricing might contain pricing rules, helper functions, and pure calculations. It does not need PricingService, PricingUtils, and PricingCalculator classes just to organize behavior.

Require Aliases Keep Call Sites Clear

Prefer explicit aliases for namespaces you call frequently.

 1(ns order-service.handler
 2  (:require
 3   [order-service.pricing :as pricing]
 4   [order-service.validation :as validation]))
 5
 6(defn handle-price-request [request]
 7  (let [order (:body request)]
 8    (if-let [errors (seq (validation/order-errors order))]
 9      {:status 400
10       :body {:errors errors}}
11      {:status 200
12       :body {:total (pricing/total order)}})))

This is more maintainable than pulling many names into the current namespace with :refer :all. Java developers often like broad imports because IDE navigation can hide the cost. In Clojure, qualified names communicate ownership directly in the code.

Common Loading Failures

Symptom How to debug
Could not locate order_service/pricing... Check that the file is under a classpath root such as src/, then verify dots, slashes, hyphens, and underscores.
Function appears undefined after edit Reload the namespace, evaluate the file again, or use your editor’s reload command.
Circular require error Move shared functions to a lower-level namespace or invert the dependency.

Practice

Create src/order_service/pricing.clj and require it from a REPL.

1(ns order-service.pricing)
2
3(defn total [order]
4  (reduce + (map :price (:items order))))

Then evaluate:

1(require '[order-service.pricing :as pricing])
2
3(pricing/total {:items [{:price 12} {:price 8}]})
4;; => 20

If the require fails, debug the path first. If the call fails after require succeeds, debug the var name, argument shape, or namespace reload state.

Knowledge Check

### Where should `order-service.pricing` normally live? - [x] `src/order_service/pricing.clj` - [ ] `src/order-service/pricing.clj` - [ ] `src/order/service/pricing.clj` - [ ] `test/order_service/pricing_test.clj` > **Explanation:** Dots become directories and hyphens in namespace segments become underscores in file paths. The `test/` path is appropriate for a test namespace, not the production namespace. ### Why is `[clojure.string :as str]` usually better than referring every string function into the current namespace? - [x] Calls such as `str/trim` show which namespace owns the function - [ ] It makes the string functions compile into Java methods - [ ] It changes `clojure.string` into a local file - [ ] It disables namespace loading > **Explanation:** Aliases keep call sites explicit while avoiding long fully qualified names. They improve readability without hiding function ownership. ### A `require` cannot locate `order_service/pricing`. What should you check first? - [x] The file path and classpath root - [ ] The number of functions in the namespace - [ ] Whether the file starts with a Java `package` declaration - [ ] Whether the namespace name is capitalized > **Explanation:** Most namespace loading failures are path and classpath problems. Verify the resource path before looking for deeper language issues.
Revised on Saturday, May 23, 2026