nREPL Server
Starting a server
There are many ways to start an nREPL server. Most often you’d start it using some build tool, but you can also embed the server in your application and start it from it. Here we’ll outline the most popular options available for you today.
Using Clojure CLI tools
| This section assumes you’re using Clojure 1.9+. |
If you’re into the clj command you can take advantage of nREPL’s built-in command-line interface
(nrepl.cmdline).
Add this alias to ~/.clojure/deps.edn:
{
;; ...
:aliases {:nREPL
{:extra-deps
{nrepl/nrepl {:mvn/version "1.7.0"}}}}
}
Then you can simply run the nREPL server in headless mode like this:
$ clj -M:nREPL -m nrepl.cmdline
A good practice is to add whatever nREPL middleware you want to use to
the nREPL profile, so you can easily activate them when needed. Here’s
how you can easily start a ClojureScript capable nREPL:
{
;; ...
:aliases {:nREPL
{:extra-deps
{nrepl/nrepl {:mvn/version "1.7.0"}
cider/piggieback {:mvn/version "0.6.1"}}}}
}
$ clj -M:nREPL -m nrepl.cmdline --middleware "[cider.piggieback/wrap-cljs-repl]"
By default, nREPL listens for connections on a randomly chosen local port with no authentication, but you can specify the address and port explicitly, or you can ask it to listen on a UNIX domain (filesystem) socket instead. This is supported natively on JDK 17 and newer; for earlier versions you need to add a junixsocket dependency:
{
;; ...
:aliases {:nREPL
{:extra-deps
{nrepl/nrepl {:mvn/version "1.7.0"}
com.kohlschutter.junixsocket/junixsocket-core {:mvn/version "2.10.1"}}}}
}
$ mkdir -m go-rwx nrepl-test
$ clj -M:nREPL -m nrepl.cmdline --socket nrepl-test/socket
| UNIX domain sockets know that they’re running locally, so they can avoid some checks and operations (like routing), which makes them faster and lighter than TCP/IP sockets. In practice this means they are a better option for local development. |
Here’s a listing of all the options available via nREPL’s command-line
interface (this output was simply generated with --help):
-i/--interactive Start nREPL and connect to it with the built-in client. -c/--connect Connect to a running nREPL with the built-in client. -C/--color Use colors to differentiate values from output in the REPL. Must be combined with --interactive. -b/--bind ADDR Bind address, by default "127.0.0.1". -h/--host ADDR Host address to connect to when using --connect. Defaults to "127.0.0.1". -p/--port PORT Start nREPL on PORT. Defaults to 0 (random port) if not specified. -s/--socket PATH Start nREPL on filesystem socket at PATH. --ack ACK-PORT Acknowledge the port of this server to another nREPL server running on ACK-PORT. -n/--handler HANDLER The nREPL message handler to use for each incoming connection; defaults to the result of `(nrepl.server/default-handler)`. Must be expressed as a namespace-qualified symbol. The underlying var will be automatically `require`d. -m/--middleware MIDDLEWARE A sequence of vars (expressed as namespace-qualified symbols), representing middleware you wish to mix in to the nREPL handler. The underlying vars will be automatically `require`d. If unavailable, the server will exit unless symbols are marked with ^:optional metadata. -t/--transport TRANSPORT The transport to use (default `nrepl.transport/bencode`), expressed as a namespace-qualified symbol. The underlying var will be automatically `require`d. --help Show this help message.
|
When defining an alias in
Some additional details on the subject can be found here. |
Using Leiningen
Leiningen has built-in support for nREPL. Just do:
$ lein repl
And you’re all set. By default Leiningen will also connect to the running nREPL server using the popular command-line nREPL client REPL-y. If you don’t need the terminal REPL you can also start nREPL in headless mode:
$ lein repl :headless
This will start nREPL listening on localhost and a random port. You can specify the host and port explicitly like this:
$ lein repl :headless :host some-host :port 1234
Alternatively you can bind nREPL to a Unix domain socket like this:
$ lein repl :headless :socket path/to/socket
| You’ll need Leiningen 2.9.9 or newer for the command above to work. |
| As noted earlier - using Unix sockets improves your security, as that’s a connection that’s guaranteed to be local. You should always be careful not to start nREPL on a public host and port. |
You can also specify a lot of nREPL’s configuration options in the :repl profile in your project.clj file:
;; Options to change the way the REPL behaves.
:repl-options {;; Specify the string to print when prompting for input.
;; defaults to something like (fn [ns] (str *ns* "=> "))
:prompt (fn [ns] (str "your command for <" ns ">? " ))
;; What to print when the repl session starts.
:welcome (println "Welcome to the magical world of the repl!")
;; Specify the ns to start the REPL in (overrides :main in
;; this case only)
:init-ns foo.bar
;; This expression will run when first opening a REPL, in the
;; namespace from :init-ns or :main if specified.
:init (println "here we are in" *ns*)
;; Print stack traces on exceptions (highly recommended, but
;; currently overwrites *1, *2, etc).
:caught clj-stacktrace.repl/pst+
;; Skip's the default requires and printed help message.
:skip-default-init false
;; Customize the socket the repl task listens on and
;; attaches to. Specify either a filesystem :socket
;; (where the parent directory should be used to
;; control access since POSIX doesn't require
;; respecting the socket permissions):
:socket "/path/to/the/socket"
;; or a network :host and/or :port
;; :host "0.0.0.0"
;; :port 4001
;; If nREPL takes too long to load it may timeout,
;; increase this to wait longer before timing out.
;; Defaults to 30000 (30 seconds)
:timeout 40000
;; nREPL server customization
;; Only one of #{:nrepl-handler :nrepl-middleware}
;; may be used at a time.
;; Use a different server-side nREPL handler.
:nrepl-handler (nrepl.server/default-handler)
;; Add server-side middleware to nREPL stack.
:nrepl-middleware [my.nrepl.thing/wrap-amazingness
;; TODO: link to more detailed documentation.
;; Middleware without appropriate metadata
;; (see nrepl.middleware/set-descriptor!
;; for details) will simply be appended to the stack
;; of middleware (rather than ordered based on its
;; expectations and requirements).
(fn [handler]
(fn [& args]
(prn :middle args)
(apply handler args)))]}
Refer to Leiningen’s sample.project.clj for an up-to-date version of those options.
|
You can see the version of nREPL used by Leiningen in the message that it will display once the REPL has been started.
Here you can see that Leiningen has started an nREPL 1.7.0 server and has connected to it using REPL-y 0.4.3. |
Using Gradle
The Clojurephant plugin provides Clojure/ClojureScript support for Gradle, including built-in nREPL support:
$ ./gradlew clojureRepl
| Clojurephant will only start an nREPL server. You will need to use a separate nREPL client, such as your editor. |
See Clojurephant’s docs for other configuration options.
Embedding nREPL
All the above options are typically used during the development of an application. It can also be extremely useful to have your application host a REPL server wherever it might be deployed; this can greatly simplify debugging, sanity-checking, panicked code patching, and so on.
| You should think long and hard before hot-patching code in production, but that’s a subject for an unrelated discussion. |
nREPL provides a socket-based server that you can trivially start from your application. Add it to your project’s dependencies, and add code like this to your app:
=> (require '[nrepl.server :refer [start-server stop-server]])
nil
=> (defonce server (start-server :port 7888))
='user/server
If you want your nREPL server to listen on a particular address instead of the
default one, you can use the :bind keyword to specify the address to
listen on. E.g., to make the nREPL server listen on address 172.18.0.5
and port 4001:
(defonce server (start-server :bind "172.18.0.5" :port 4001))
| Keep in mind that running a nREPL server on a public address is an epic security hole! As the connections are insecure (no authentication, no authorization) by default, anyone can connect to your app and modify its behaviour or run code on the remote host. |
You can also ask nREPL to listen on a UNIX domain (filesystem) socket
with the :socket keyword (if you’re using JDK 17 or newer add a
junixsocket dependency),
which should be as secure as the access to the socket's parent
directories (POSIX doesn’t specify the effect of the socket file’s
permissions (if any), and some systems have ignored them):
(defonce server (start-server :socket "/some/where/safe/nrepl"))
Depending on what the lifecycle of your application is, whether you want to be
able to easily restart the server, etc., you might want to put the value
start-server returns into an atom or somesuch. Anyway, once your app is
running an nREPL server, you can connect to it from a tool like Leiningen or
Counterclockwise or REPL-y, or from another Clojure process, as shown
here.
You can stop the server with (stop-server server).
Embedding in a Java application
Embedding nREPL in an existing Java application can also be useful - if your Java code allows for it you can still introspect values and call methods while the app is running. Since nREPL currently does not have a Java API, you’ll need to use Clojure’s interop features.
import clojure.java.api.Clojure;
import clojure.lang.IFn;
public class App {
public static void main(String[] args) {
IFn require = Clojure.var("clojure.core", "require");
require.invoke(Clojure.read("nrepl.server"));
IFn start = Clojure.var("nrepl.server", "start-server");
int port = 7888;
start.invoke(Clojure.read(":port"), Clojure.read(Integer.toString(port)));
System.out.println("nrepl server started on port " + port);
}
}
You can pull in the needed dependencies by adding this to your pom.xml:
<dependencies>
<dependency>
<groupId>nrepl</groupId>
<artifactId>nrepl</artifactId>
<version>1.7.0</version>
</dependency>
<dependency>
<groupId>org.clojure</groupId>
<artifactId>clojure</artifactId>
<version>1.12.3</version>
</dependency>
</dependencies>
A more complete example, including the use of custom middleware, can be found here.
Server discovery
When nREPL starts via the CLI (i.e. nrepl.cmdline), it writes the
server port to a file so that editors and other tools can automatically
find and connect to the running server.
.nrepl-port
The server port is written to .nrepl-port in the current working
directory. Most Clojure editors — CIDER, Calva, Cursive, Conjure, and
others — look for this file to auto-connect to a running nREPL server.
The file is registered for deletion on JVM shutdown (via
File.deleteOnExit), so under normal circumstances it is cleaned up
automatically when the server stops.
.nrepl-tls-proxy-port
When using TLS with a local proxy, the proxy port is written to
.nrepl-tls-proxy-port in the current working directory. See
TLS for details on setting up TLS connections.
Embedded servers
nrepl.server/start-server does not write a port file — only the
CLI does. If you are embedding nREPL in your application and want editors
to auto-discover the server, you need to write the port file yourself:
|
(let [server (nrepl.server/start-server)]
(spit ".nrepl-port" (:port server)))
Server options
Note that nREPL is not limited to its default messaging protocol, nor to its default use of sockets. nREPL provides a transport abstraction for implementing support for alternative protocols and connection methods. Alternative transport implementations are available, and implementing your own is not difficult; read more about transports here.
Server Configuration
Starting with version 0.5, you can configure certain aspects of the nREPL server’s behaviour via configuration files.
There are two configuration files:
-
Global configuration file
~/.nrepl/nrepl.edn -
Local configuration file
.nrepl.edn
The global configuration file is useful for setting options that you’d
like to use for all the nREPL servers that you start (e.g. a common
bind-address, transport, handler, etc).
Set NREPL_CONFIG_DIR to override the global config directory.
Otherwise nREPL uses $XDG_CONFIG_HOME/nrepl, then ~/.config/nrepl,
and finally defaults to ~/.nrepl.
|
The local configuration file should be placed in the directory from
which you’re starting the server (normally the root directory of your
project). Its purpose is to set project-specific settings (e.g. a common port
you always want to use with a particular project). Any setting in .nrepl.edn
will take precedence over a setting in .nrepl/nrepl.edn.
Here’s an example global configuration file:
{:bind "::"
:transport nrepl.transport/tty
:middleware [some.ns/mw1 some.ns/mw2]}
| You should refer to vars only as symbols. |
And this is an example of a local config file:
{:bind "localhost"
:port 12345
:ack 23456
:handler some.ns/awesome-handler
:transport nrepl.transport/bencode}
Configuration keys
The following keys are recognized in .nrepl.edn and ~/.nrepl/nrepl.edn:
| Key | Type | Default | Description |
|---|---|---|---|
|
integer |
|
Port to listen on. |
|
string |
|
Address to bind the server socket to. |
|
string |
— |
Filesystem path for a Unix domain socket (alternative to |
|
symbol |
— |
A namespace-qualified symbol resolving to a handler function. When set, |
|
symbol |
|
A namespace-qualified symbol resolving to a transport function (e.g. |
|
vector of symbols |
|
Middleware to include in the handler. Symbols can be marked with |
|
integer |
— |
Port of an already-running nREPL server to notify of this server’s port. |
|
map |
|
A map from fully-qualified var symbols to default values applied to new sessions. See [_dynamic_var_defaults]. |
|
string |
— |
Path to a file containing certificates and private key for TLS. See TLS. |
|
string |
— |
Inline string containing certificates and private key for TLS. |
|
boolean |
|
Whether to load the native JVMTI agent for thread stopping on Java 21+. See JVMTI agent. |
|
symbol |
— |
A namespace-qualified symbol resolving to a custom REPL function (used with |
Merging CLI and config values (^:concat)
Settings passed via the command-line interface replace configuration
file settings by default. For example, if your config file specifies
:middleware [a/mw1 a/mw2] and you pass --middleware "[b/mw3]" on the
CLI, the resulting middleware will be just [b/mw3] — the config file
values are lost.
Since version 1.6, you can use the ^:concat metadata on collection values
to concatenate them instead of replacing. The metadata can be placed on
either the config value or the CLI value — if either side carries
^:concat, the values are merged with into (config first, then CLI).
For example, given this config file:
{:middleware ^:concat [company/metrics company/security]}
And this CLI invocation:
$ clj -M:nREPL -m nrepl.cmdline --middleware "[cider.nrepl/cider-middleware]"
The resulting middleware will be:
[company/metrics company/security cider.nrepl/cider-middleware]
This is useful for ensuring certain middleware (e.g. company-wide monitoring or security middleware) is always loaded, even when development tools pass their own middleware via CLI flags.
^:concat only applies when merging CLI arguments with config file
values. It does not affect the merge between global (~/.nrepl/nrepl.edn)
and local (.nrepl.edn) config files — local values always replace
global values.
|
Dynamic var defaults
You can use the server configuration file (either project-local or system-wide) to specify default values for dynamic variables. These defaults are applied when a new session is created (i.e. when a client connects). This serves two important purposes:
-
Gives a starting value to the dynamic variable.
-
Makes it rebindable thread-locally via
set!.
The format is a map from fully-qualified var symbols to their values:
{:dynamic-vars {clojure.core/*warn-on-reflection* true
clojure.core/*print-length* 100
clojure.core/*print-level* 5}}
You can configure any dynamic var whose namespace is loaded at server
startup. This includes all of clojure.core and any namespace that your
project or middleware loads eagerly.
nREPL sessions already bind a set of vars by default (with their current root values):
-
*warn-on-reflection*,*unchecked-math*,*assert* -
*print-meta*,*print-length*,*print-level*,*print-namespace-maps* -
*data-readers*,*default-data-reader-fn*,*read-eval* -
*math-context*,*compile-path*,*command-line-args*
The :dynamic-vars config overrides these defaults and can also introduce
additional vars. For example, you could set a project-local default for
*print-length* to prevent accidental printing of large sequences:
{:dynamic-vars {clojure.core/*print-length* 50}}
| The var’s namespace must already be loaded when the session is created. If a symbol cannot be resolved, nREPL logs a warning and skips it. |
Optional middleware (^:optional)
By default, the server will exit with an error if any configured middleware
cannot be resolved (i.e. its namespace isn’t on the classpath). Since
version 1.6, middleware symbols can be marked with ^:optional metadata — if the middleware can’t be loaded, it is silently skipped and the server
starts with the remaining middleware.
{:middleware [cider.nrepl/cider-middleware
^:optional experimental.ns/beta-feature]}
You can also use ^:optional from the command line:
$ clj -M:nREPL -m nrepl.cmdline --middleware "[cider.nrepl/cider-middleware,^:optional experimental.ns/beta-feature]"
This is useful when you share a single nREPL configuration across projects that don’t all have the same dependencies on the classpath. For instance, a global config can reference editor-specific middleware as optional so it works regardless of which projects include that dependency:
{:middleware ^:concat [^:optional cider.nrepl/cider-middleware
^:optional some.other/editor-middleware]}
^:optional only affects middleware resolution at startup — if
the middleware’s namespace exists on the classpath but the var is missing
or throws an error during loading, the server will still fail.
|