Where Should My user.clj File Go?

Sat, Apr 27, 2019 by @camdez

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 classpath , 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.clj 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:

It needs to be at the root somewhere on the classpath to be autoloaded.

It shouldn’t be somewhere where it will get bundled into build artifacts.

You likely don’t want the file committed to version control.

Given the problem involves construction of the classpath and generation of artifacts, you’ll need to leverage your build system for this.

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.