Browse Clojure Foundations for Java Developers

Hot Reloading Code

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:

  • you are not only recompiling classes
  • you are reloading namespaces in a live runtime
  • some values may persist across reloads
  • some code may need explicit restart logic after a reload

If you do not understand those boundaries, the session becomes confusing quickly.

The Smallest Reload Tool: require With Reload Flags

The 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:

  • you know exactly which namespace changed
  • you want explicit control
  • the reload impact is small and understandable

It is not always the best tool once a project grows and namespace dependencies become harder to track manually.

Reloading Changes Code, Not Necessarily System State

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:

  • new function definitions
  • old running resources
  • state created by earlier code

all in the same session.

That is why serious REPL workflows usually pair reloadable code with explicit restart helpers.

Use clojure.tools.namespace.repl/refresh Deliberately

When 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:

  • scans source code directories for changed files
  • reloads them in dependency order
  • defaults to scanning all directories on the Java classpath unless you set refresh dirs explicitly

That last point matters more than many beginners realize.

Restrict What refresh Scans

The 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.

Use Restart Functions With Refresh

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:

  • reload the changed namespaces
  • then run a known restart function

That pattern is often better than hoping the previous runtime state still matches the new code definitions.

Example: A Practical Development Flow

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:

  • “classes were reloaded somehow”

and:

  • “the code changed, then the development system was restarted on purpose”

Only the second one is really trustworthy at scale.

Reloading Works Best With REPL-Friendly Design

Hot reload is easiest when:

  • namespace load does not trigger huge side effects automatically
  • long-lived resources are started and stopped by functions, not by incidental top-level evaluation
  • the core logic is mostly pure
  • stateful edges are explicit

It becomes painful when:

  • namespace load starts servers and jobs implicitly
  • state is hidden in arbitrary globals
  • initialization order is fragile
  • reload and restart logic are mixed together informally

If hot reload is constantly weird, the problem is often architectural, not just tooling.

When A Full Restart Is The Better Choice

Not every situation deserves heroic reload logic.

Restart the whole session when:

  • too much hidden state accumulated
  • reload order got confusing
  • you changed initialization assumptions drastically
  • the session stopped being trustworthy

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.

Common Mistakes

  • assuming reload automatically restarts resources
  • using refresh without constraining refresh dirs
  • reloading namespaces with heavy load-time side effects
  • trying to save every session instead of restarting when the context is no longer reliable

Hot reload is valuable, but only when you understand what remains alive after the code changes.

Knowledge Check: Reloading Without Lying To Yourself

### What does `require` with `:reload` help you do? - [x] Reload a namespace's code into the current session - [ ] Restart every running resource automatically - [ ] Rebuild the JDK - [ ] Convert all project files to bytecode manually > **Explanation:** `require` with reload flags updates code definitions in the running session, but it does not automatically manage your application's runtime state. ### Why is `clojure.tools.namespace.repl/refresh` often more convenient than manual reload calls? - [x] It reloads changed namespaces in dependency order. - [ ] It permanently saves every REPL session. - [ ] It removes the need for namespaces. - [ ] It disables all side effects. > **Explanation:** `refresh` tracks changed files and reloads the relevant namespaces in dependency order, which is much easier than managing that sequence by hand. ### Why is `set-refresh-dirs` a good practice? - [x] Because otherwise `refresh` defaults to scanning all directories on the Java classpath. - [ ] Because Clojure cannot read `src/` by default. - [ ] Because it automatically writes tests. - [ ] Because it disables reloading entirely. > **Explanation:** The current tools.namespace API docs explicitly note that `refresh` defaults to scanning all directories on the Java classpath unless you set refresh dirs. ### What problem does `refresh :after 'my.app.dev/restart` solve? - [x] It connects code reload with an intentional runtime restart step. - [ ] It recompiles Java classes into Kotlin. - [ ] It makes namespaces unnecessary. - [ ] It prevents all REPL exceptions. > **Explanation:** Reloading code and restarting resources are different concerns. The `:after` hook helps keep them coordinated. ### When is a full REPL restart the better move? - [x] When hidden state or changed assumptions make the current session untrustworthy - [ ] Never; a good Clojure developer should avoid restarting entirely - [ ] Only after publishing to production - [ ] Only when using Leiningen > **Explanation:** A full restart is often the safest option once the live session no longer reflects a coherent program state.
Revised on Friday, April 24, 2026