Your First Clojure Program
The traditional first Clojure program is a simple (println "Hello, World!") evaluated at the REPL or run as a script. While trivial on its own, successfully running it confirms your JDK, Clojure CLI or Leiningen setup, and basic syntax understanding all work together correctly before you attempt to write anything more substantial.
Cricket analogy: Writing your first Clojure program, a simple (println "Hello, World!"), is like a young cricketer's first delivery in the nets before ever facing a real match — it's a small, low-stakes action, but successfully executing it confirms your basic mechanics all actually work together before you attempt anything more complex.
Structuring a Namespace
Every non-trivial Clojure source file begins with an ns declaration naming its namespace and requiring the other namespaces it depends on, typically using an :as alias for brevity: (ns my-app.core (:require [clojure.string :as str])). This lets code later refer to a required function as str/upper-case rather than the fully qualified clojure.string/upper-case.
Cricket analogy: A Clojure namespace declared with (ns my-app.core (:require [clojure.string :as str])) is like a franchise's squad list for a season, naming the team and which specific overseas players it has drafted in, aliased under a shorter squad tag (str) for quick reference on the scoreboard.
Writing and Composing Functions
Idiomatic Clojure programs are built from small, single-purpose functions composed together, often using the -> (thread-first) or ->> (thread-last) macros to make a sequence of transformations read top-to-bottom instead of as deeply nested calls. For example, (-> raw-input parse-command execute-command render-result) threads a value through four functions in a clear, linear order.
Cricket analogy: Composing small functions together, like feeding the output of (parse-scorecard raw-text) into (calculate-run-rate parsed-data), is like a bowling attack working in tandem — the new-ball bowler sets up the conditions that the first-change bowler then exploits, each doing one clearly defined job that builds on the previous one's output.
Running Your Program
By convention, a Clojure program's entry point is a function named -main, which receives command-line arguments as a sequence of strings. Leiningen projects run it with lein run, while Clojure CLI projects use clj -M -m my-app.core; both approaches load the specified namespace and invoke -main to start execution.
Cricket analogy: Designating a -main function as the program's entry point is like naming the designated opening batter who walks out first to face the very first ball of the innings — every Clojure program run via lein run or clj -M -m my-app.core starts execution at -main, the same way every innings always officially begins with the openers.
;; src/my_app/core.clj
(ns my-app.core
(:require [clojure.string :as str]))
(defn shout [msg]
(str/upper-case msg))
(defn greet [name]
(str "Hello, " name "! Welcome to Clojure."))
(defn -main
[& args]
(println (greet "World"))
(println (shout "let's build something")))
;; Run it with:
;; lein run
;; or:
;; clj -M -m my-app.core
;;
;; Output:
;; Hello, World! Welcome to Clojure.
;; LET'S BUILD SOMETHINGNamespace names must exactly match their file path and use underscores in the filename where the namespace uses hyphens — a namespace declared as (ns my-app.core) must live in a file at src/my_app/core.clj. Mismatching hyphens and underscores between the namespace and the filename is one of the most common errors beginners hit.
- A Clojure source file begins with an
nsdeclaration naming its namespace and requiring any needed libraries. - The
:requireclause imports other namespaces, often with a short:asalias for convenience. - Small, focused functions can be composed together, often using the
->threading macro, to build larger programs. - The
-mainfunction is the conventional entry point executed when a program is run. lein runorclj -M -m namespace.namebuilds and executes a program starting from-main.- Namespace names must match their file path exactly, with underscores replacing hyphens in filenames.
- Structuring code into small, composed functions makes programs easier to test and reason about.
Practice what you learned
1. What must every Clojure source file typically begin with?
2. What does the `:require [clojure.string :as str]` clause do?
3. Which function is the conventional entry point for running a Clojure program?
4. If a namespace is declared as `(ns my-app.core)`, where must the corresponding file live?
5. What is the purpose of the `->` threading macro when composing functions?
Was this page helpful?
You May Also Like
The REPL and Forms
How Clojure's Read-Eval-Print Loop works, how forms are evaluated, and why REPL-driven development is central to the Clojure workflow.
Clojure Syntax Basics
A tour of Clojure's core syntax: s-expressions, literal data types, the four collection types, and how to define values and functions.
Installing Clojure and Leiningen
A step-by-step guide to setting up a working Clojure development environment, including the JDK, the Clojure CLI tools, and Leiningen.
Related Reading
Related Study Notes in Programming
Browse all study notesApache Spark Study Notes
Programming · 30 topics
ProgrammingApache Flink Study Notes
Programming · 30 topics
ProgrammingHadoop Study Notes
Programming · 30 topics
ProgrammingSnowflake Study Notes
Programming · 30 topics
ProgrammingApache Airflow Study Notes
Programming · 30 topics
Programmingdbt (Data Build Tool) Study Notes
Programming · 30 topics