Reload changed namespaces deliberately, and use tools.namespace only when you understand what it will scan and restart.
One of the most satisfying parts of REPL-based development is changing code without throwing away the whole running session. But “hot reloading” is only pleasant when you understand exactly what is being reloaded and what state is being preserved.
For Java developers, this is a key shift:
If you do not understand those boundaries, the session becomes confusing quickly.
require With Reload FlagsThe most direct way to reload a namespace is require with a reload option:
1(require 'my.app.core :reload)
Or, when you prefer a vector-style libspec:
1(require '[my.app.core :as core] :reload)
When dependencies also need to be reloaded:
1(require 'my.app.core :reload-all)
This is a good tool when:
It is not always the best tool once a project grows and namespace dependencies become harder to track manually.
This is the most important practical point.
Suppose you have:
1(defonce server (atom nil))
and a namespace function that starts a web server and stores it in that atom.
Reloading the namespace does not automatically stop and restart the server in the way you probably intend. You may have:
all in the same session.
That is why serious REPL workflows usually pair reloadable code with explicit restart helpers.
clojure.tools.namespace.repl/refresh DeliberatelyWhen manual require ... :reload becomes tedious, many projects use clojure.tools.namespace.repl/refresh:
1(require '[clojure.tools.namespace.repl :refer [refresh]])
Then:
1(refresh)
According to the current tools.namespace API docs, refresh:
That last point matters more than many beginners realize.
refresh ScansThe API docs explicitly say the directories scanned by refresh are controlled by set-refresh-dirs, and that otherwise it defaults to all directories on the Java classpath.
That is usually broader than you want.
A safer setup is:
1(require '[clojure.tools.namespace.repl :refer [refresh set-refresh-dirs]])
2
3(set-refresh-dirs "src" "test" "dev")
Now refresh is working over the directories you actually care about in development.
This is an especially important habit in larger projects, because it makes reload behavior more predictable and avoids surprising scans of unrelated classpath directories.
refresh also supports an :after option:
1(refresh :after 'my.app.dev/restart)
This is one of the cleanest ways to keep a live session honest:
That pattern is often better than hoping the previous runtime state still matches the new code definitions.
Imagine a small service with a dev namespace:
1(ns my.app.dev
2 (:require [clojure.tools.namespace.repl :refer [refresh set-refresh-dirs]]
3 [my.app.system :as system]))
4
5(defonce running-system (atom nil))
6
7(set-refresh-dirs "src" "test" "dev")
8
9(defn start []
10 (reset! running-system (system/start)))
11
12(defn stop []
13 (when-let [s @running-system]
14 (system/stop s)
15 (reset! running-system nil)))
16
17(defn restart []
18 (stop)
19 (start))
20
21(defn reset []
22 (refresh :after 'my.app.dev/restart))
This is a strong pattern because it makes the relationship between code reload and runtime restart explicit.
For a Java engineer, think of it as the difference between:
and:
Only the second one is really trustworthy at scale.
Hot reload is easiest when:
It becomes painful when:
If hot reload is constantly weird, the problem is often architectural, not just tooling.
Not every situation deserves heroic reload logic.
Restart the whole session when:
A full restart is not failure. It is often the cleanest move. Good REPL habits include knowing when to stop patching a dirty live session.
refresh without constraining refresh dirsHot reload is valuable, but only when you understand what remains alive after the code changes.