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.
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.
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 Leiningensrc/ contains application codetest/ mirrors the namespace layout for testsresources/ holds non-code assets and configThis should already feel familiar to Java engineers who have seen src/main and src/test separation, even though the exact directory names differ.
This is one of the first Clojure filesystem conventions that surprises Java developers:
So:
my-stuff.coresrc/my_stuff/core.cljThis mapping is normal. Internalize it early and you will avoid a lot of namespace/file confusion.
project.cljA 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 packagingYou 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.
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:
That is enough to confirm the project’s entry point is wired correctly.
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:
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 OptionalMany 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.
The important shift is not the syntax of project.clj. It is the tighter feedback loop:
That loop is usually faster and more interactive than the compile-package-run rhythm many Java developers are used to.
lein run is the only development entry pointproject.clj without understanding the effect on the REPL or runtime classpathUse the template to learn the workflow, not to dictate your entire design.