Browse Clojure Foundations for Java Developers

Creating a Project with Leiningen

Generate a real Leiningen app, understand the file layout, and learn the first commands that matter.

The fastest way to understand Leiningen is to generate a project and touch the files it creates. For Java developers, this is the closest Clojure equivalent to generating a starter Maven or Gradle project, then reading the build file and source layout.

Step 1: Create The Project

Run:

1lein new app my-stuff

The app template is for applications rather than libraries. If you omit app, Leiningen uses the default template, which is more library-oriented.

This command gives you a working skeleton immediately. That matters because the goal here is not to admire the template. It is to get to a project-aware REPL and a runnable main namespace quickly.

Step 2: Read The Layout

A fresh project will look roughly like this:

 1my-stuff/
 2├── project.clj
 3├── README.md
 4├── src/
 5│   └── my_stuff/
 6│       └── core.clj
 7├── test/
 8│   └── my_stuff/
 9│       └── core_test.clj
10└── resources/

The important parts:

  • project.clj describes the project to Leiningen
  • src/ contains application code
  • test/ mirrors the namespace layout for tests
  • resources/ holds non-code assets and config

This should already feel familiar to Java engineers who have seen src/main and src/test separation, even though the exact directory names differ.

Step 3: Learn The Dash-To-Underscore Mapping

This is one of the first Clojure filesystem conventions that surprises Java developers:

  • project and namespace names often use dashes
  • file paths use underscores

So:

  • namespace: my-stuff.core
  • file path: src/my_stuff/core.clj

This mapping is normal. Internalize it early and you will avoid a lot of namespace/file confusion.

Step 4: Inspect project.clj

A new application project will include a project.clj similar to this:

1(defproject my-stuff "0.1.0-SNAPSHOT"
2  :description "FIXME: write description"
3  :dependencies [[org.clojure/clojure "1.12.4"]]
4  :main ^:skip-aot my-stuff.core
5  :target-path "target/%s"
6  :profiles {:uberjar {:aot :all}})

The keys to notice first:

  • defproject gives the project name and version
  • :dependencies pulls in Clojure and any libraries you add
  • :main tells lein run what namespace to invoke
  • :profiles controls alternate builds such as uberjar packaging

You do not need to master the whole file on day one. You do need to know where to look when the project needs a new library or a different entry point.

Step 5: Edit The Main Namespace

Open src/my_stuff/core.clj. A fresh app template typically gives you a small -main.

Replace the placeholder behavior with something slightly more realistic:

1(ns my-stuff.core
2  (:gen-class))
3
4(defn -main [& args]
5  (println "Starting app")
6  (println "Args:" args))

This example is intentionally small, but it does two useful things:

  • proves the generated app actually runs
  • makes command-line arguments visible immediately

That is enough to confirm the project’s entry point is wired correctly.

Step 6: Run The App

From the project root:

1lein run

If everything is healthy, Leiningen will resolve dependencies as needed, compile what it must, and run the -main function.

This is a useful moment to notice a Clojure difference from Java:

  • you do not need to pre-build a giant artifact before testing a tiny change
  • the run loop and the REPL loop are closely connected

Step 7: Run Tests And Start A REPL

After lein run, the next two commands to learn are:

1lein test
2lein repl

lein test confirms the project skeleton is wired correctly.

lein repl is the more important long-term habit. It gives you a REPL with your project’s classpath and dependencies already loaded, which is exactly what you want during development.

Inside that REPL, you can require your namespace and call functions directly:

1(require '[my-stuff.core :as core])
2(core/-main "demo")

That is the inner loop you should care about, not only whether lein run works.

lein deps Is Usually Optional

Many Java developers expect to run a separate “download dependencies” step first. With Leiningen, dependency resolution normally happens on demand when you run tasks.

You can use:

1lein deps

when you explicitly want to fetch dependencies ahead of time, but it is not always required as a separate step.

What To Notice As A Java Engineer

The important shift is not the syntax of project.clj. It is the tighter feedback loop:

  • generate project
  • edit namespace
  • run app
  • open project REPL
  • call functions directly

That loop is usually faster and more interactive than the compile-package-run rhythm many Java developers are used to.

Common Mistakes

  • forgetting the dash-to-underscore namespace/file mapping
  • assuming lein run is the only development entry point
  • editing project.clj without understanding the effect on the REPL or runtime classpath
  • treating the generated template as production architecture instead of a starting point

Use the template to learn the workflow, not to dictate your entire design.

Knowledge Check: First Steps With A Leiningen App

### Which command creates a new application project with Leiningen? - [x] `lein new app my-stuff` - [ ] `lein create app my-stuff` - [ ] `clojure -X:new app my-stuff` - [ ] `lein init app my-stuff` > **Explanation:** `lein new app my-stuff` creates a new application project from the `app` template. ### What file is the main project configuration file in a Leiningen app? - [x] `project.clj` - [ ] `deps.edn` - [ ] `app.clj` - [ ] `build.clj` > **Explanation:** Leiningen uses `project.clj` as the main configuration file for dependencies, metadata, and project behavior. ### If the namespace is `my-stuff.core`, what is the usual file path? - [x] `src/my_stuff/core.clj` - [ ] `src/my-stuff/core.clj` - [ ] `src/core/my-stuff.clj` - [ ] `my-stuff/src/core.clj` > **Explanation:** Namespace names often use dashes, while file paths use underscores in the corresponding directory segments. ### Which command starts a REPL with the project's dependencies loaded? - [x] `lein repl` - [ ] `lein run` - [ ] `lein deps` - [ ] `lein clean` > **Explanation:** `lein repl` starts a project-aware REPL, which is one of the most useful parts of the Leiningen workflow. ### Why is `lein repl` more important than just `lein run` for daily development? - [x] It lets you interact with project code directly and shorten the feedback loop. - [ ] It permanently compiles the application into native code. - [ ] It replaces the need for source files. - [ ] It disables dependency management. > **Explanation:** The project REPL is what makes development interactive. You can load namespaces, call functions, and inspect values without treating every change like a full application restart.
Revised on Friday, April 24, 2026