Create a small Clojure project with a namespace, pure greeting function, runnable entry point, and repeatable command while mapping each piece to familiar Java concepts.
A useful first Clojure project should do more than print text. It should show how source files map to namespaces, how a pure function is called from an entry point, and how the JVM classpath is assembled from project configuration.
For Java engineers, the smallest valuable project has four parts:
| Project piece | Java comparison | Why it matters |
|---|---|---|
deps.edn |
pom.xml or build.gradle, but smaller |
Declares source paths, dependencies, and aliases |
src/hello_world/core.clj |
src/main/java/.../Main.java |
Holds the namespace and functions |
hello-world.core/-main |
public static void main(String[] args) |
Gives the JVM a command-line entry point |
clojure -M -m ... |
java, Maven exec, or Gradle run task |
Runs code on the calculated classpath |
Start with a plain directory. The Clojure CLI does not require a generated scaffold for a tiny program.
1mkdir hello-world
2cd hello-world
3mkdir -p src/hello_world
Create deps.edn:
1{:paths ["src"]
2 :deps {org.clojure/clojure {:mvn/version "1.12.4"}}
3 :aliases
4 {:run {:main-opts ["-m" "hello-world.core"]}}}
Read this as data, not XML:
| Key | Meaning |
|---|---|
:paths |
Directories added to the JVM classpath |
:deps |
Maven or Git dependencies |
:aliases |
Named classpath or command variations |
:main-opts |
Command-line options used by an alias |
Create src/hello_world/core.clj:
1(ns hello-world.core)
2
3(defn greeting
4 "Return a greeting instead of printing so the function stays easy to test."
5 [name]
6 (str "Hello, " name "!"))
7
8(defn -main
9 [& args]
10 (let [name (or (first args) "Clojure")]
11 (println (greeting name))))
The hyphen/underscore rule is the first Clojure file-system detail Java developers usually notice:
| Namespace form | File path |
|---|---|
hello-world.core |
src/hello_world/core.clj |
| Hyphen in namespace segment | Underscore in directory or file segment |
| Dot in namespace | Directory boundary |
The greeting function returns data. The -main function performs the side effect. Keep that separation early; it makes testing and REPL-driven development much easier.
Run the main namespace directly:
1clojure -M -m hello-world.core Java
Or use the alias from deps.edn:
1clojure -M:run Java
Expected output:
1Hello, Java!
The command does three things:
deps.edn.src and dependencies.hello-world.core and calls -main.The Java version puts class identity and entry-point behavior in one class:
1public class HelloWorld {
2 public static String greeting(String name) {
3 return "Hello, " + name + "!";
4 }
5
6 public static void main(String[] args) {
7 String name = args.length > 0 ? args[0] : "Java";
8 System.out.println(greeting(name));
9 }
10}
The Clojure version separates the same concerns differently:
| Concern | Java habit | Clojure habit |
|---|---|---|
| Namespace/package | Package plus class name | Namespace maps to file path |
| Pure behavior | Static method or instance method | Plain function |
| Side effect | main method |
-main function |
| Project config | Build file owns classpath | deps.edn owns classpath |
If your team uses Leiningen, the equivalent scaffold is:
1lein new app hello-world
2cd hello-world
3lein run Java
Do not rewrite a project from Leiningen to deps.edn just to learn Clojure. Learn the project you have, then choose the right tool when you start a new codebase.
greeting to accept a map such as {:name "Ada" :language "Clojure"}.farewell function without changing -main.clojure -Spath and identify where src appears in the classpath.core.clj to a different namespace and fix the file path to match.-main thin.deps.edn is data that describes paths, dependencies, and command aliases.