Setting up Scala 3: Coursier, Scala CLI and sbt
Install the toolchain with Coursier, run scripts with Scala CLI, and lay out an sbt project you can build and test from the terminal.
Coursier and the toolchain
Coursier is the installer that the Scala ecosystem itself recommends. It fetches the JVM, Scala CLI, sbt and scalafmt, and keeps them on a predictable path. Install it once and the rest of the toolchain becomes one command each.
# install Coursier, then the tools
curl -fL https://github.com/coursier/coursier/releases/latest/download/cs-x86_64-pc-linux.gz | gzip -d > cs
chmod +x cs && ./cs setup # adds Coursier to PATH and installs the basics
cs install scala-cli sbt scalafmt
cs java --jvm temurin:21 # install and print the JAVA_HOME of a JDK
eval "$(cs java --env --jvm 21)" # put that JDK on PATH for this shell
scala-cli version
sbt --version
scala --version| Tool | What it is | Use it for |
|---|---|---|
scala-cli | Compile, run, test and package a set of files | Scripts, katas, small tools, prototyping |
sbt | The long-standing build tool | Multi-module application builds and publishing |
mill | A faster alternative build tool | Large builds where sbt startup annoys you |
scalafmt | The formatter | Formatting on save and in CI |
Metals | The language server | VS Code, Neovim, any LSP editor |
Scala 3 / Scala 2.13 | Two language lines | New code targets Scala 3 |
Scala CLI declares its dependencies inside the source file with directives, which removes the need for a build file while you are exploring an idea.
//> using scala 3.5.0
//> using dep com.lihaoyi::os-lib:0.10.7
//> using test.dep org.scalameta::munit:1.0.0
import java.nio.file.Files
@main def demo(): Unit =
val files = os.list(os.pwd).take(5)
println(files.mkString(", "))scala-cli run demo.scala
scala-cli test .
scala-cli package . -o app --assembly # a runnable fat jar
scala-cli replAn sbt project layout
project/
build.properties # sbt version
plugins.sbt # plugin declarations
src/
main/scala/ # production sources in packages
main/resources/ # files loaded from the classpath
test/scala/ # tests
build.sbt # the build definition// project/build.properties
// sbt.version=1.10.2
// build.sbt
ThisBuild / scalaVersion := "3.5.0"
ThisBuild / organization := "com.example"
ThisBuild / version := "0.1.0-SNAPSHOT"
lazy val root = (project in file("."))
.settings(
name := "shop",
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-core" % "2.12.0",
"com.lihaoyi" %% "os-lib" % "0.10.7",
"org.scalameta" %% "munit" % "1.0.0" % Test
),
scalacOptions ++= Seq(
"-deprecation",
"-feature",
"-unchecked",
"-Wunused:all",
"-Werror"
),
Compile / run / fork := true
)%%appends the Scala binary version to the artifact name, socats-core_3andcats-core_2.13are different artifacts. Use%for a pure Java library that has no Scala variant.- Every sbt command is a task or a setting, and a setting is only evaluated once per session.
reloadafter editingbuild.sbt;~compilerecompiles on change. -Werrorwith-Wunused:allis the Scala 3 equivalent of turning warnings into a build gate. Add it from the first commit.- Use
ThisBuild /for settings that should apply to every subproject, and per-project.settings(...)for the rest.
sbt compile
sbt "testOnly com.example.PricingSuite"
sbt run
sbt console # a REPL with the project on the classpath
sbt scalafmtCheck # formatting gate for CIEditor support
Metals is the language server behind VS Code and most other editors. It imports the build through Bloop, which produces a compilation database that is also useful for continuous compilation.
# VS Code: install the Metals extension and run "Metals: Import build"
# Neovim: install nvim-metals and point it at your sbt or Scala CLI build
# IntelliJ IDEA: use the Scala plugin; it has its own compiler
# a .scalafmt.conf keeps everyone's diffs small
cat > .scalafmt.conf <<'EOF'
version = "3.8.1"
runner.dialect = scala3
maxColumn = 100
EOF
scalafmt --test # fails when a file is unformattedFAQ
Scala 2 or Scala 3?
Why is the first compile so slow?
Related
Syntax and immutability sbt, dependencies and project structure
Last refreshed 2026-09-18.