01 · Setup & First Program¶
Install the JDK and sbt¶
Scala runs on the Java Virtual Machine (JVM), so you need a JDK installed first, then Scala's own tooling on top. Install a recent JDK (17 or 21 LTS) and sbt (the standard Scala build tool):
# macOS (Homebrew)
brew install openjdk@21
brew install sbt
# Ubuntu/Debian
sudo apt install openjdk-21-jdk
echo "deb https://repo.scala-sbt.org/scalasbt/debian all main" | sudo tee /etc/apt/sources.list.d/sbt.list
sudo apt update && sudo apt install sbt
# Windows: use the installer from https://adoptium.net for the JDK,
# then https://www.scala-sbt.org/download.html for sbt
Verify the install:
Most Scala installations (via sbt, Coursier, or
an IDE) also give you the scala command for running the REPL and scripts
directly.
The Scala REPL¶
The REPL (Read-Eval-Print Loop) is the fastest way to try things out. Launch
it by typing scala in your terminal:
scala> val greeting = "Hello, Scala!"
// val greeting: String = Hello, Scala!
scala> println(greeting)
// Hello, Scala!
scala> 2 + 2
// val res0: Int = 4
scala> :quit
val res0 is the REPL auto-naming an unnamed expression's result so you can
refer to it later (res0, res1, ...). This is a REPL-only convenience, not
something you write in real source files.
Your first program with sbt¶
Real projects use sbt to manage dependencies, compilation, and running. Create a new project directory with this layout:
// build.sbt
ThisBuild / scalaVersion := "3.3.3"
lazy val root = (project in file("."))
.settings(
name := "hello-scala"
)
Run it with sbt:
@main marks a top-level method as a program entry point (Scala 3) — no
wrapping class or object boilerplate required, unlike Java's
public static void main.
Single-file scripts (quick experiments)¶
For quick one-off scripts without a full sbt project, use the scala-cli
tool (bundled with recent Scala installs) or plain scala on a .scala
file:
This compiles and runs in one step — handy for small scripts, but real projects still use sbt (see Module 9 of Level 2 for sbt in depth).
Anatomy of the program¶
| Piece | Meaning |
|---|---|
@main def hello(): Unit = ... |
Declares hello as a runnable entry point. Unit means "returns nothing useful," Scala's equivalent of void. |
println(...) |
Prints text followed by a newline to standard output. |
build.sbt |
Declares the project's Scala version, name, and dependencies. |
src/main/scala/ |
Where sbt looks for your .scala source files by convention. |
Choosing an IDE¶
IntelliJ IDEA with the Scala plugin is the most common choice — solid refactoring, debugging, and sbt integration. VS Code with the "Metals" extension (a Scala language server) is a strong free/lightweight alternative. Either works well for everything in this course.
How It Actually Works¶
sbt run isn't one step — it's a pipeline. First the Scala compiler
(scalac, invoked by sbt's incremental compiler, Zinc) parses Hello.scala
into an abstract syntax tree, type-checks it, and lowers it to JVM
bytecode — .class files under target/scala-3.x/classes/. @main def
hello(): Unit = ... doesn't compile to a bare function (the JVM has no
concept of a free-floating function); the compiler synthesizes a real class
(something like hello) with a public static void main(String[] args)
method whose body calls your hello() logic, because the JVM's process
entry point has always been "a class with a main method," a convention
inherited from Java. That's the actual mechanism @main hides from you.
Zinc's incrementality is why the second sbt run is fast: it tracks which
source files' compiled output (bytecode + a dependency graph of which
classes reference which) is still valid, and only recompiles files whose
source or transitive dependencies changed — full whole-project recompiles
happen only on a clean build or a change that ripples widely (e.g. editing a
trait extended everywhere).
Once compiled, java (which sbt run shells out to under the hood, with the
project's dependency jars on the classpath) loads the class, and the JVM's
class loader resolves println to scala.Predef.println, which itself
delegates to System.out.println — Scala's "standard library extras" layer
sitting on top of the JVM's own java.lang/java.io classes. This is also
why Scala interoperates so smoothly with Java libraries: after compilation
there's no meaningful difference between a .class file that came from
.scala source and one that came from .java source — the JVM only sees
bytecode.
🔀 See this in another language¶
Exercise¶
Create a new sbt project named greeter. Add a @main entry point that
prints a greeting for three different names, one per line. Run it with
sbt run, then also try running the same logic directly in the scala REPL
by pasting the println lines in one at a time.