Where Should My user.clj File Go?

Clojure programmers often use a user.clj file to contain bits of development tooling and code experiments that are not considered part of the underlying project. This file has the “magical” property that it is automatically loaded by Clojure as it starts up, without needing to be referenced explicitly in the project’s code.

Clojure will happily load this file from anywhere on the classpath1, which begs the question: where should I put my user.clj file?

In general, I think the answer is simple: dev/user.clj.

You’ll then want to make sure dev/ is on your classpath.

If you use Leiningen that means adding to the :profiles section in your project.clj file:

(defproject my-app "0.0.1"
  :profiles {:dev {:source-paths ["dev"]}}

Lastly, you’ll probably want to add dev/user.clj to your .gitignore file.

You’re all set! Unless you’re vehemently disagreeing, in which case, read on!

Rationale (or, “why not src/user.clj”?)

It’s tempting to just drop the user.clj file into src/ because things will immediately work given a freshly-generated project.clj2 file. Resist the urge.

Don’t put your user.clj in the src/ directory as it will get bundled into your production artifacts. At best, this is wasteful. At worst, it’s a security threat (imagine if you have sensitive information in there like API or encryption keys).

user.clj is magical but not too magical. Its magical power is that it automatically gets loaded if found on the classpath, but it’s not magically excluded from artifacts. When you run lein uberjar it will get bundled in like any other file under src/—this is usually not something you want!

You have some flexibility in how you arrange things, but the basic constraints of how to handle the user.clj file are:

You’re Probably Wondering How I Ended Up Here…

For a while I was getting an exception whenever I ran lein uberjar. The exception was clearly related to the fact that I had a src/user.clj file and the fact that it requires reloaded/repl, which is not a part of the production app (read: uberjar) that is being built. Despite that, I didn’t really know how others were not running into this issue. I felt like I had a typical setup.

$ lein uberjar
Exception in thread "main" java.lang.ExceptionInInitializerError
        at clojure.main.<clinit>(main.java:20)
Caused by: Syntax error compiling at (user.clj:1:1).
        at clojure.lang.Compiler.load(Compiler.java:7647)
        at clojure.lang.RT.loadResourceScript(RT.java:381)
        at clojure.lang.RT.loadResourceScript(RT.java:368)
        at clojure.lang.RT.maybeLoadResourceScript(RT.java:364)
        at clojure.lang.RT.doInit(RT.java:485)
        at clojure.lang.RT.<clinit>(RT.java:338)
        ... 1 more
Caused by: java.io.FileNotFoundException: Could not locate reloaded/repl__init.class, reloaded/repl.clj or reloaded/repl.cljc on classpath.
Compilation failed: Subprocess failed

My gross workaround was to move user.clj out of the src/ directory whenever I built an uberjar. Which sounds horrible, but my uberjars are typically built by our CI server, so I didn’t have to do this too often (typically only if I was working on the automated build process itself).

I didn’t really understand what was going on here until I started learning about Boot and thinking more deeply about what tools like Boot and Leiningen actually do. Until you do so, it’s easy to gloss over the boundaries between Clojure itself and the build tools. Moreover, classpaths aren’t a thing one thinks about much when everything is going swell.

As it turned out user.clj was getting picked up by lein uberjar. Which was throwing exceptions because my user.clj relies on reloaded.repl, which is brought in via a dependency in my ~/lein/profiles.clj file which is (appropriately) not available on the classpath when building for production.

Upon reflection, it’s fairly logical that src/user.clj would get pulled into the classpath and stuffed into the deployment uberjar, but I think one could be forgiven for thinking user.clj is a magical development convenience and the “right thing” (exclusion from production build artifacts) would just happen.

Given that that isn’t the case, I’d suggest either taking the time to learn Clojure’s build tools fairly well, or following the simple formula presented at the beginning of this post.

  1. The classpath is an important concept Clojure inherits from Java—it’s essentially a list of places the JVM looks to find things (esp. classes). These “places” are searched sequentially, giving the logical appearance of a single, unified tree containing all of the things you provide as well as all of the things provided by your dependencies. One of the principal duties of a tool like Leiningen is to construct this classpath, typically from a combination of file directories and dependency information. The classpath tends to be constructed differently depending on the circumstances (e.g. interactive development, test runs, generating production artifacts); Leiningen’s primary mechanism for representing these circumstances / variants is “profiles”, which are conditionally activated to collectively achieve the desired behavior.

  2. The rest of this post assumes you’re using Leiningen. It’s hard to address this topic without making some assumptions about tooling because classpaths, file organization, and build tooling are all intertwined (dare I say “complected”?), but I think dev/user.clj is likely to be a fine place to store that file regardless of your build tooling.