Browse Learn Clojure Foundations as a Java Developer

Create a Leiningen Project

Generate a Leiningen application, read its project.clj and namespace layout, run the app, run tests, and start the project REPL without confusing the scaffold with architecture.

The fastest way to understand Leiningen is to create a small app and run each command once. Treat the scaffold as a learning object, not as the architecture you must use forever.

Create the App

Run:

1lein new app orders-demo
2cd orders-demo

The app template creates an application with a -main entry point. Without app, Leiningen creates a more library-oriented skeleton.

Inspect the Layout

You should see a structure like this:

 1orders-demo/
 2├── project.clj
 3├── README.md
 4├── resources/
 5├── src/
 6│   └── orders_demo/
 7│       └── core.clj
 8└── test/
 9    └── orders_demo/
10        └── core_test.clj
File or directory Why it matters
project.clj Owns dependencies, project metadata, main namespace, profiles, and plugins
src/orders_demo/core.clj Production namespace generated from the project name
test/orders_demo/core_test.clj Test namespace matching the source namespace
resources/ Classpath resources such as config templates or logging files
target/ Generated output after tests, compilation, or packaging

Notice the dash-to-underscore mapping:

Name Form
Project name orders-demo
Namespace orders-demo.core
Directory path orders_demo/core.clj

Read the Generated project.clj

A generated app usually resembles:

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

For a Java engineer, the useful reading order is:

  1. Project name and version.
  2. Clojure and library dependencies.
  3. Main namespace.
  4. Packaging profile.
  5. Any team-specific plugins or profiles.

Do not edit every placeholder immediately. First prove that the generated project runs.

Make the Main Namespace Useful

Open src/orders_demo/core.clj and keep the side effect at the edge:

 1(ns orders-demo.core
 2  (:gen-class))
 3
 4(defn summarize-order [order]
 5  (str "Order " (:id order) " has " (:line-count order) " lines"))
 6
 7(defn -main
 8  [& args]
 9  (println
10   (summarize-order {:id (or (first args) "demo")
11                     :line-count 3})))

This tiny design matters:

Function Role
summarize-order Pure behavior you can call from tests or REPL
-main Runtime boundary that handles command-line input and printing

Run the Application

From the project root:

1lein run 1001

Expected output:

1Order 1001 has 3 lines

If dependencies are not downloaded yet, Leiningen will resolve them as part of the task. That is why Java developers do not usually need a separate “install dependencies first” step for ordinary local work.

Add a Test

Open test/orders_demo/core_test.clj and test the pure function:

1(ns orders-demo.core-test
2  (:require [clojure.test :refer [deftest is testing]]
3            [orders-demo.core :as core]))
4
5(deftest summarize-order-test
6  (testing "summarizes an order"
7    (is (= "Order 1001 has 3 lines"
8           (core/summarize-order {:id "1001" :line-count 3})))))

Run:

1lein test

The goal is to establish the same basic loop you expect in Java: edit code, run tests, keep the boundary thin.

Start the Project REPL

Run:

1lein repl

Inside the REPL:

1(require '[orders-demo.core :as core])
2(core/summarize-order {:id "repl" :line-count 2})

This is the Clojure inner loop. You are not only proving that main runs; you are loading the project and calling functions directly.

Package Only After the Loop Works

Once run and test commands are clean:

1lein uberjar

Then verify the generated standalone jar with java -jar. Packaging should confirm a working app, not compensate for an unclear project structure.

Key Takeaways

  • lein new app gives you a small runnable app, not a full architecture.
  • Read project.clj before changing it.
  • Keep business logic in normal functions and keep -main small.
  • Use lein test and lein repl early, not only lein run.
  • Package with lein uberjar only after the local loop works.

Quiz: Leiningen Project

### Which command creates a Leiningen application scaffold? - [x] `lein new app orders-demo` - [ ] `lein init app orders-demo` - [ ] `clojure -T:new orders-demo` - [ ] `mvn archetype:clojure` > **Explanation:** `lein new app` creates an application-oriented Leiningen project. ### What is the usual namespace for `src/orders_demo/core.clj`? - [x] `orders-demo.core` - [ ] `orders_demo.core` - [ ] `orders.demo.core` - [ ] `src.orders-demo.core` > **Explanation:** The namespace uses a hyphen, while the file path uses an underscore. ### Why should `-main` stay thin? - [x] Pure functions are easier to test and call from the REPL. - [ ] Clojure forbids logic inside `-main`. - [ ] Leiningen ignores functions called from `-main`. - [ ] The JVM cannot pass arguments to `-main`. > **Explanation:** Keeping side effects at the boundary makes the project easier to test and inspect interactively.
Revised on Saturday, May 23, 2026