Browse Learn Clojure Foundations as a Java Developer

Build Your First Clojure Project

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

Create the Project Directory

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

Add the First Namespace

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 It

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:

  1. Reads deps.edn.
  2. Builds a classpath containing src and dependencies.
  3. Loads hello-world.core and calls -main.

Compare It With Java

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

Use Leiningen If the Project Already Does

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.

Practice

  1. Change greeting to accept a map such as {:name "Ada" :language "Clojure"}.
  2. Add a farewell function without changing -main.
  3. Run clojure -Spath and identify where src appears in the classpath.
  4. Move core.clj to a different namespace and fix the file path to match.

Key Takeaways

  • A first Clojure project is mostly a namespace, a classpath, and a command.
  • Use pure functions for behavior and keep -main thin.
  • deps.edn is data that describes paths, dependencies, and command aliases.
  • Hyphens in namespaces become underscores in file paths.
  • Java entry-point instincts transfer, but Clojure encourages smaller functions around the entry point.

Quiz: First Clojure Project

### What should a tiny first Clojure project demonstrate beyond printing text? - [x] Namespace mapping, a pure function, an entry point, and a repeatable run command - [ ] A full web framework and database - [ ] A custom Maven plugin - [ ] A macro-heavy DSL > **Explanation:** A first project should expose the project mechanics you will reuse: namespaces, classpath, functions, and commands. ### Why does `hello-world.core` live at `src/hello_world/core.clj`? - [x] Clojure maps dots to directories and hyphens to underscores in file paths. - [ ] Clojure requires all namespaces to use underscores. - [ ] Java package rules require it. - [ ] Leiningen ignores namespace names. > **Explanation:** Clojure namespaces use hyphens naturally, but file paths use underscores for those namespace segments. ### Why should the greeting logic return a string instead of printing directly? - [x] It keeps behavior easy to test and leaves side effects in `-main`. - [ ] Printing is not supported by Clojure. - [ ] Strings cannot be passed to `println`. - [ ] The JVM requires every function to return `void`. > **Explanation:** Returning data keeps the core function pure; printing belongs at the boundary.
Revised on Saturday, May 23, 2026