Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Scala 3  Module 1 – Start here: Scala 3.9 LTS on the JVM

Install Scala 3.9, a JDK and an editor

Install the scala command with Coursier, choose a JDK 17 or newer and control which one runs your program, pin Scala 3.9 per project and open it in an editor.

  • Beginner
  • 15 minutes
  • Examples run with scala 3.9.0, Java 25.0.4.1 and Python 3.14.8
  • By MySmartCoPilot
Free: 15 minutes

What you will learn

  • Install the scala command with cs setup and check it with scala version and java -version
  • Choose a JDK 17 or newer and control which JDK runs your program with JAVA_HOME or --java-home
  • Set the Scala version of a project to 3.9.0 with a using directive in project.scala
  • Recognise the launcher's JDK 25 warning lines and tell them apart from your program's output
  • Set up VS Code with Metals or IntelliJ IDEA for a Scala CLI project with scala setup-ide

Before you start

What you’ll learn · Before you start

What you will learn

  • Install the scala command with cs setup and check it with scala version and java -version
  • Choose a JDK 17 or newer and control which JDK runs your program with JAVA_HOME or --java-home
  • Set the Scala version of a project to 3.9.0 with a using directive in project.scala
  • Recognise the launcher's JDK 25 warning lines and tell them apart from your program's output
  • Set up VS Code with Metals or IntelliJ IDEA for a Scala CLI project with scala setup-ide

Before you start

On this page

To compile and run Scala 3.9 you need two programs on your computer: a JDK 17 or newer, and the scala command, which since Scala 3.5 is Scala CLI. In this track’s recorded runs, scala version reports Scala CLI 1.16.0 with Scala 3.9.0, and java -version reports Temurin 25.0.4.1.

Installing takes one command, Coursier’s cs setup, but three choices decide whether your runs match these pages: the JDK that runs your program, the Scala version each project compiles with, and the editor that reads the project. This lesson installs the tools and checks them, shows which JDK runs what, pins Scala 3.9.0 for a project, explains the WARNING lines that JDK 25 adds, and connects VS Code or IntelliJ IDEA.

A JDK, the scala command and an editor do three jobs

Each of the three pieces has one job, and one quick check that tells you it works:

Piece What it does How you check it
A JDK: The Java Development Kit: a JVM together with the Java compiler and tools such as javap. Scala 3.8 and later need JDK 17 or newer to compile and run. 17 or newer runs the scala command itself and every program it builds java -version
The scala command, Scala CLI: The tool behind the scala command since Scala 3.5. It fetches compilers and libraries, compiles, runs, tests and packages code, and opens the REPL. fetches the compiler and libraries, compiles, runs, tests, opens the REPL scala version
An editor: VS Code with Metals, or IntelliJ IDEA shows errors, types and completions while you type it opens the project’s files without errors

The JDK comes first because everything else runs on it: Scala CLI is itself a Java program, and from Scala 3.8 on, compiling and running Scala need JDK 17 or newer. This track uses Eclipse Temurin 25, a long-term support build of JDK 25, but any JDK 17 or newer runs the same programs.

Scala CLI does not ship every compiler version inside it. The first time a project asks for a Scala version or a library, it downloads what it needs from Maven Central into a cache in your home folder, and later runs reuse the cache. That is why the first run of a new project takes longer than the rest, and why a computer without internet can run only what it has fetched before.

Checkpoint

Check: JDK, scala command and editor

  1. One question On a train without internet, yesterday's project still runs, but a new project that adds a library you have never used stops at its first scala run. Which piece explains it?

    Choose one answer.

    A hint

    Think about what the first run of a project fetches, and where it keeps it.

    Show the answer: One question

    Answer: The scala command, which downloads compilers and libraries on first use and keeps them in a cache

    Scala CLI fetches what a project asks for from Maven Central the first time and keeps it in a cache in your home folder. Offline, a project runs only if everything it needs is already there.

Install with Coursier, then check scala and java

The installation page of the Scala documentation recommends Coursier: The artifact fetcher and installer of the Scala world. cs setup installs the scala command and its tools, and cs java installs and selects JDKs., whose command is cs. Run the route for your system, then open a new terminal so that its PATH includes the new commands:

  • macOS with Homebrew: brew install coursier && coursier setup.
  • macOS without Homebrew, or Linux: download the cs binary for your processor as the installation page shows, make it executable with chmod +x cs, and run ./cs setup.
  • Windows: download the Coursier installer from the installation page, run it, and answer its questions.

cs setup installs scala, scalac, sbt and scalafmt, adds them to your PATH, and installs a JVM if it finds none, after asking you. A version manager such as mise, which can install both Scala and JDKs, is another route: in a project folder, mise use scala@3.9.0 java@temurin-25 installs both and records them in the folder’s mise.toml. Step through the install, including the two ways back when a check fails:

Interactive · stage by stage Install with Coursier, then check two versions Step through the install. The two arrows back are what to do when a check does not print what it should.
Install with Coursier, then check two versions, step 8 of 8. Showing Get cs for your system, Run cs setup, Open a new terminal, scala version, java -version, Ready for this track. In focus: Ready for this track.command not foundolder than 17Get cs for your systembrew, curl or the installerRun cs setupscala, scalac, sbt, a JVMOpen a new terminalso PATH has the new toolsscala versionthe runner and its defaultjava -version17 or newer?Ready for this trackpin 3.9.0 in each project17 or newer

The drawing shows the whole scene; its 8 steps are listed below.

Every step as text
  1. Get cs. Coursier's command is cs. On a Mac with Homebrew, brew install coursier installs it; elsewhere you download the cs binary for your processor, or the Windows installer, from the installation page.
  2. cs setup. cs setup installs scala, scalac, sbt and scalafmt, adds them to your PATH, and installs a JVM if it finds none, after asking you.
  3. A new terminal. A terminal reads PATH when it opens, so one that was already open does not know the new commands yet. Open a new one before you check anything.
  4. Check scala. scala version prints two lines: the Scala CLI behind the command, and the Scala version it uses by default. A fresh install defaults to the newest Scala Next release.
  5. Not found?. scala: command not found almost always means a terminal that was opened before the install. Open a new one and check again; if it persists, log out and back in.
  6. Check the JDK. java -version names the JDK your terminal finds first. Scala 3.9 needs 17 or newer, so 17, 21 or 25 pass and 8 or 11 do not.
  7. Older than 17?. An older JDK came first on PATH. Run cs setup again with a newer JVM, for example cs setup --jvm temurin:25, or install Temurin 25 yourself and put it first on PATH.
  8. Ready. Both checks pass. The default Scala version of the command does not matter from here on, because each project of this track pins 3.9.0 itself.

Now the check. scala version names the Scala CLI behind the command and the Scala version it uses by default, and java -version names the JDK that your terminal finds first. This session shows everything the two commands printed on JDK 25, the first four lines included:

scala version and java -version on JDK 25 Python · versions_session.py
from session import run

# The check after an install: the scala command and the JDK, on JDK 25, with the launcher's warnings shown.
run("""
scala version
java -version
""", warnings=True)

Output

$ scala version
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
WARNING: sun.misc.Unsafe::objectFieldOffset has been called by scala.runtime.LazyVals$ (file:…/scala/3.9.0/libexec/scala-cli.jar)
WARNING: Please consider reporting this to the maintainers of class scala.runtime.LazyVals$
WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release
Scala code runner version: 1.16.0
Scala version (default): 3.9.0
$ java -version
openjdk version "25.0.4.1" LTS
OpenJDK Runtime Environment Temurin-25.0.4.1+1 (build 25.0.4.1+1-LTS)
OpenJDK 64-Bit Server VM Temurin-25.0.4.1+1 (build 25.0.4.1+1-LTS, mixed mode, sharing)

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 versions_session.py

Leave the four WARNING lines for the next section. Scala code runner version: 1.16.0 is Scala CLI, and Scala version (default): 3.9.0 is the compiler it uses for a project that names no version. On your computer, cs setup installs the newest release, so after a fresh install the default is the newest Scala Next, 3.10.0 with Scala CLI 1.17.1, according to the 3.10.0 release notes. That is fine: the section after next pins 3.9.0 for each project, so the default never decides. The JDK’s first line ends with the build’s release date on a real terminal; the session leaves the date out.

Checkpoint

Check: Installing and checking

  1. Question 1 of 2 You ran cs setup, and in the same terminal window scala version says scala: command not found. What do you do first?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Open a new terminal and run the check again

    cs setup adds its folder to PATH in your shell's profile files, and a terminal reads them when it opens. Open a new terminal; if the command is still missing, log out and back in.

  2. Question 2 of 2 After a fresh install, scala version prints Scala version (default): 3.10.0, but this track uses 3.9.0. What do you do?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Nothing to the install; each project pins 3.9.0 with a using directive

    cs setup installs the newest release, so its default is the newest Scala Next. A project pins its own version in project.scala, and the default never decides for it.

The launcher and your program can run on different JDKs

The scala command is a Java program, and it starts another JVM for your program. The two need not be the same JDK. This session runs the version report of the previous lesson four times, changing one setting each time:

Which JDK runs the launcher, and which runs the program Python · jvms_session.py
from session import run

# Which JDK runs what: the launcher runs on the first java on PATH, the program on JAVA_HOME or --java-home.
run("""
cd report
scala run . --server=false
export JAVA_HOME={jdk:21}
scala run . --server=false
export PATH="$JAVA_HOME/bin:$PATH"
scala run . --server=false
scala run . --server=false --java-home {jdk:17}
""", warnings=True)

Output

$ cd report
$ scala run . --server=false
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
WARNING: sun.misc.Unsafe::objectFieldOffset has been called by scala.runtime.LazyVals$ (file:…/scala/3.9.0/libexec/scala-cli.jar)
WARNING: Please consider reporting this to the maintainers of class scala.runtime.LazyVals$
WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release
WARNING: A restricted method in java.lang.System has been called
WARNING: java.lang.System::load has been called by com.sun.jna.Native in an unnamed module (file:…/scala/3.9.0/libexec/scala-cli.jar)
WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
WARNING: Restricted methods will be blocked in a future release unless native access is enabled

Scala library   3.9.0
Java runtime    25.0.4.1 (Eclipse Adoptium)
Platform        Mac OS X, aarch64
$ export JAVA_HOME=…/java/temurin-21.0.12+101.0.LTS
$ scala run . --server=false
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called
WARNING: sun.misc.Unsafe::objectFieldOffset has been called by scala.runtime.LazyVals$ (file:…/scala/3.9.0/libexec/scala-cli.jar)
WARNING: Please consider reporting this to the maintainers of class scala.runtime.LazyVals$
WARNING: sun.misc.Unsafe::objectFieldOffset will be removed in a future release
WARNING: A restricted method in java.lang.System has been called
WARNING: java.lang.System::load has been called by com.sun.jna.Native in an unnamed module (file:…/scala/3.9.0/libexec/scala-cli.jar)
WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
WARNING: Restricted methods will be blocked in a future release unless native access is enabled

Scala library   3.9.0
Java runtime    21.0.12.1 (Eclipse Adoptium)
Platform        Mac OS X, aarch64
$ export PATH="$JAVA_HOME/bin:$PATH"
$ scala run . --server=false
Scala library   3.9.0
Java runtime    21.0.12.1 (Eclipse Adoptium)
Platform        Mac OS X, aarch64
$ scala run . --server=false --java-home …/java/temurin-17.0.20+101
Scala library   3.9.0
Java runtime    17.0.20.1 (Eclipse Adoptium)
Platform        Mac OS X, aarch64

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 jvms_session.py

Follow the Java runtime line and the WARNING lines together:

  1. With JDK 25 first on PATH and in JAVA_HOME: An environment variable that names the folder of a JDK, the one that holds bin. Scala CLI runs your program on the JDK it names unless --java-home names another., both JVMs are JDK 25, and the warnings appear.
  2. With only JAVA_HOME moved to JDK 21, the program reports 21, but the warnings stay: the launcher still runs on the java it found on PATH, JDK 25.
  3. With JDK 21 first on PATH too, the warnings are gone, because the launcher now runs on JDK 21.
  4. --java-home overrides JAVA_HOME for one run, and the program reports 17 while the launcher stays on 21.

So the launcher follows PATH, and your program follows JAVA_HOME, unless --java-home, or the directive //> using javaHome in a source file, names another JDK. A third way, //> using jvm 21 or --jvm 21, asks Scala CLI to fetch a JVM through Coursier; cs java --available lists the JDKs it can fetch. Step through the four runs:

Interactive · message by message The launcher's JVM and your program's JVM Step through the four runs of the session. Watch which JDK runs the launcher, which runs your program, and when the warnings appear.
The launcher's JVM and your program's JVM, step 6 of 6. Messages so far: Your shell to scala launcher: java on PATH; scala launcher to Your program: JAVA_HOME; scala launcher to Your program: --java-home 17.Your shellPATH 21, JAVA_HOME 21scala launcherruns on JDK 21Your programJava runtime 17.0.20.1java on PATHJAVA_HOME--java-home 17

The drawing shows the whole scene; its 6 steps are listed below.

Every step as text
  1. Run 1: the launcher. The shell finds the first java on PATH, JDK 25, and starts scala-cli.jar with it. That JVM is the launcher's, whatever JAVA_HOME says.
  2. Run 1: the warnings. JDK 25 warns about two things inside scala-cli.jar, a sun.misc.Unsafe method and a native library, and prints eight WARNING lines before anything else.
  3. Run 1: the program. Once the report is compiled, the launcher starts a JVM of its own for it on the JDK of JAVA_HOME, here JDK 25 as well.
  4. Run 2: JAVA_HOME 21. With only JAVA_HOME moved to JDK 21, the program reports 21, but the launcher still runs on the JDK 25 of PATH, so the warnings stay.
  5. Run 3: PATH 21 too. With JDK 21 first on PATH as well, the launcher runs on JDK 21. JDK 21 has neither warning, so the run prints only the report.
  6. Run 4: --java-home 17. --java-home names a JDK for this one run. The launcher stays on JDK 21, and the program runs on JDK 17, which Scala 3.9 still supports.

Reading java -version to find the JDK that runs your program

java -version reports the JDK on PATH, which runs the launcher. Your program runs on the JDK of JAVA_HOME, which in the second run above was 21 while java -version would have said 25. Print java.version from the program, as the version report does, or keep PATH and JAVA_HOME on the same JDK.

The WARNING lines come from Scala CLI, not from your code

The eight lines are two warnings that JDK 24 introduced, and JDK 25 prints them too. The first four come from JEP 498: some code called a memory-access method of sun.misc.Unsafe, here scala.runtime.LazyVals inside scala-cli.jar. The next four come from JEP 472: some code loaded a native library without native access enabled, here JNA inside the same jar. Both name scala-cli.jar, so they describe the tool, and your program is not involved. They are harmless today, and JDKs older than 24 do not print them at all: with the launcher on JDK 21, the third run showed none.

To switch them off on JDK 25, give every java that Scala CLI starts the two options that JEP 498 and JEP 472 name, through the JDK_JAVA_OPTIONS environment variable, which the java launcher adds to its command line:

The two warnings switched off Python · quiet_session.py
from session import run

# The launcher's two JDK 25 warnings switched off for every JVM that the scala command starts.
run("""
cd report
export JDK_JAVA_OPTIONS="--sun-misc-unsafe-memory-access=allow --enable-native-access=ALL-UNNAMED"
scala run . --server=false
""", warnings=True)

Output

$ cd report
$ export JDK_JAVA_OPTIONS="--sun-misc-unsafe-memory-access=allow --enable-native-access=ALL-UNNAMED"
$ scala run . --server=false
NOTE: Picked up JDK_JAVA_OPTIONS: --sun-misc-unsafe-memory-access=allow --enable-native-access=ALL-UNNAMED
NOTE: Picked up JDK_JAVA_OPTIONS: --sun-misc-unsafe-memory-access=allow --enable-native-access=ALL-UNNAMED
NOTE: Picked up JDK_JAVA_OPTIONS: --sun-misc-unsafe-memory-access=allow --enable-native-access=ALL-UNNAMED
Scala library   3.9.0
Java runtime    25.0.4.1 (Eclipse Adoptium)
Platform        Mac OS X, aarch64

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 quiet_session.py

The warnings are gone, but each JVM now prints a NOTE line instead, because the java command reminds you whenever it reads JDK_JAVA_OPTIONS. The three notes come from the launcher, from the compiler, which --server=false starts in a JVM of its own, and last from your program. Whether eight lines or three bother you more is your call; the sessions of this track simply leave the warnings out.

Key idea

The launcher runs on the first java on PATH and prints JDK 25’s warnings about itself; your program runs on the JDK of JAVA_HOME unless --java-home or a directive names another, so point both at the same JDK.

Checkpoint

Check: Which JDK runs what

  1. Question 1 of 2 A run on JDK 25 prints eight WARNING lines that name scala-cli.jar before your program's output. What are they?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: Warnings that JDK 24 and later print about code inside the launcher; your program is not involved

    JEP 498 makes the JVM warn when code calls memory-access methods of sun.misc.Unsafe, and JEP 472 when code loads a native library without native access. The launcher's jar does both, so JDK 25 warns about it.

  2. Question 2 of 2 PATH has JDK 25 first, and JAVA_HOME names JDK 21. You type scala run . for the version report. What happens?

    Choose one answer.

    A hint

    The launcher and the program each pick their JDK in their own way.

    Show the answer: Question 2 of 2

    Answer: The report says Java 21, and the eight warnings appear

    This is the second run of the session: Java runtime 21.0.12.1 with the eight warnings above it. The warnings go only when PATH, and with it the launcher, moves to JDK 21 too.

Pin Scala 3.9.0 in each project

A using directive: A comment line that starts with //> using and configures Scala CLI from inside the sources, for example //> using scala 3.9.0 to pin the compiler version. is a comment line that starts with //> using and configures Scala CLI from inside the sources. The installation page suggests keeping a project’s directives together in a file named project.scala. One line pins the compiler, whatever the default of your scala command is:

pin/project.scala Scala · pin/project.scala
//> using scala 3.9.0

The session runs a project pinned to 3.9.0, then a copy pinned to 3.10.0, on a computer without network access that has never used 3.10.0:

A pinned version, and one that must be downloaded first Python · pin_session.py
from session import run

# A project that names its Scala version, then one that asks for a version this computer has never downloaded.
run("""
cd pin
cat project.scala
scala run . --server=false
cd ../pin-newer
cat project.scala
!scala run . --server=false
""", downloads="first")

Output

$ cd pin
$ cat project.scala
//> using scala 3.9.0
$ scala run . --server=false
Scala library   3.9.0
Java runtime    25.0.4.1 (Eclipse Adoptium)
Platform        Mac OS X, aarch64
$ cd ../pin-newer
$ cat project.scala
//> using scala 3.10.0
$ scala run . --server=false
Downloading https://repo1.maven.org/maven2/org/scala-lang/scala3-library_3/maven-metadata.xml
Failed to download https://repo1.maven.org/maven2/org/scala-lang/scala3-library_3/maven-metadata.xml
[22 more lines of download attempts not shown]
[error]  Cannot find matching Scala version for '3.10.0'
You can only choose one of the 3.x, 2.13.x, and 2.12.x. versions.
The latest supported stable versions are 2.12.21, 2.13.18, 3.8.4.
In addition, you can request compilation with the last nightly versions of Scala,
by passing the 2.nightly, 2.12.nightly, 2.13.nightly, or 3.nightly arguments.
Specific Scala 2 or Scala 3 nightly versions are also accepted.
You can also request the latest Scala 3 LTS by passing lts or 3.lts.
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 pin_session.py

The first project compiled with 3.9.0, as pinned. The second failed, and its last message is misleading: Cannot find matching Scala version for '3.10.0' sounds as if 3.10.0 did not exist, and the list of “latest supported” versions that follows stops at 3.8.4. The real cause is in the first lines: Scala CLI tried to download the list of Scala 3 versions from Maven Central, and every attempt failed. With a network the same command downloads 3.10.0 once, caches it, and runs. The message also names a shortcut, lts: //> using scala lts asks for the newest Scala LTS release.

Pin an exact version such as 3.9.0 rather than lts. An exact pin gives the same compiler on every computer and in CI until you change the line, while lts moves to 3.9.1 when it is released. On the command line, --scala 3.9.0 does the same for one run.

Checkpoint

Check: Pinning the Scala version

  1. Question 1 of 2 Where does this track keep the line that pins a project to Scala 3.9.0?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: In project.scala, in the project's folder

    //> using scala 3.9.0 in the project's project.scala pins the compiler for that project, whatever the scala command's default is.

  2. Question 2 of 2 Without internet, a project pinned to 3.10.0 fails with Cannot find matching Scala version for '3.10.0'. What is the real cause?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Scala CLI needed to download 3.10.0 for the first time, and every download failed

    Read the first lines of a failure as well as the last. Here they show Scala CLI trying to download the list of Scala 3 versions from Maven Central and failing, so it could not find 3.10.0.

Editors open the project through BSP

Editors do not compile Scala themselves. They ask a build tool through BSP: The Build Server Protocol: the way an editor such as VS Code with Metals or IntelliJ IDEA asks a build tool to compile a project and report its errors., the Build Server Protocol, and Scala CLI acts as that build tool for its projects. scala setup-ide . writes the connection file that an editor reads, and so does the first scala run, compile or test of a project:

The file an editor reads to open the project Python · ide_session.py
from session import run

# What an editor reads to open a Scala CLI project: the connection file that scala setup-ide writes.
run("""
cd ide
scala setup-ide .
cat .bsp/scala.json
""")

Output

$ cd ide
$ scala setup-ide .
Wrote configuration file for ide in: .bsp/scala.json
$ cat .bsp/scala.json
{
  "name": "scala",
  "argv": [
    "…/java/temurin-25.0.4+101.0.LTS/bin/java",
    "-jar",
    "…/scala/3.9.0/libexec/scala-cli.jar",
    "--cli-default-scala-version",
    "3.9.0",
    "--repository",
    "file://…/scala/3.9.0/maven2",
    "--prog-name",
    "scala",
    "--skip-cli-updates",
    "bsp",
    "--json-options",
    ".scala-build/ide-options-v2.json",
    "--json-launcher-options",
    ".scala-build/ide-launcher-options.json",
    "--envs-file",
    ".scala-build/ide-envs.json",
    "."
  ],
  "version": "1.16.0",
  "bspVersion": "2.1.1",
  "languages": [
    "scala",
    "java"
  ]
}

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 ide_session.py

The file tells the editor how to start Scala CLI as a build server: the java of the JDK that ran setup-ide, the scala-cli.jar of the installation, the default Scala version, and the bsp command. Because it records that JDK’s path, run scala setup-ide . again after you switch JDKs. The two editors open the folder like this:

  • VS Code: install the Metals extension, then open the project folder (code .). Metals starts, imports the project, and puts run and debug buttons above each @main method. Metals itself runs on a JDK 17 or newer by default and compiles your project with the JDK of JAVA_HOME; its settings name both.
  • IntelliJ IDEA: install the Scala plugin, then choose File > Open and select the project folder. IntelliJ finds the .bsp folder and imports the project through BSP; when the import works, a BSP icon in the bottom right corner shows the connection.

Checkpoint

Check: Editors and BSP

  1. One question You moved a project from JDK 25 to JDK 21. What should you do before you open it in your editor again?

    Choose one answer.

    Show the answer: One question

    Answer: Run scala setup-ide . again, because .bsp/scala.json records the path of the JDK that wrote it

    scala setup-ide . writes .bsp/scala.json with the full path of the java that ran it. After a JDK switch, write the file again so that the editor starts Scala CLI on the new JDK.

Practise: run the setup check on your computer

This program checks the three things the rest of the track needs: a JDK 17 or newer, Scala 3.9.x and UTF-8 as the JDK’s default charset. For each check that fails it prints the fix, and it ends with a non-zero exit status, so a script can test it. Copy it into an empty folder, together with a project.scala that pins 3.9.0, and run scala run . there:

SetupCheck.scala Scala · setup-check/SetupCheck.scala
// Checks the three things this track's programs need, and says how to fix each one that fails.
import java.nio.charset.Charset

case class Check(name: String, ok: Boolean, fix: String)

def checks(): List[Check] =
  val jdk = Runtime.version().feature()
  val scalaVersion = scala.util.Properties.versionNumberString
  val charset = Charset.defaultCharset().name()
  List(
    Check(s"JDK $jdk (17 or newer)", jdk >= 17, "install a JDK 17 or newer and point JAVA_HOME at it"),
    Check(s"Scala $scalaVersion (3.9.x)", scalaVersion.startsWith("3.9."), "add //> using scala 3.9.0 to project.scala"),
    Check(s"default charset $charset", charset == "UTF-8", "use JDK 18 or newer, or set the locale to UTF-8")
  )

@main def setupCheck(): Unit =
  val results = checks()
  for c <- results do
    if c.ok then println(s"ok    ${c.name}") else println(s"FIX   ${c.name}: ${c.fix}")
  val failed = results.count(!_.ok)
  if failed == 0 then println("Ready for this track.")
  else
    println(s"$failed of ${results.size} checks failed.")
    sys.exit(1)

Here it is on JDK 25, and then on JDK 17 with the C locale:

The setup check on two JDKs Python · check_session.py
from session import run

# The setup check on JDK 25, then on JDK 17 with the C locale, where the default charset is not UTF-8.
run("""
cd setup-check
scala run . --server=false
export JAVA_HOME={jdk:17}
export LC_ALL=C
export LANG=C
!scala run . --server=false
""")

Output

$ cd setup-check
$ scala run . --server=false
ok    JDK 25 (17 or newer)
ok    Scala 3.9.0 (3.9.x)
ok    default charset UTF-8
Ready for this track.
$ export JAVA_HOME=…/java/temurin-17.0.20+101
$ export LC_ALL=C
$ export LANG=C
$ scala run . --server=false
ok    JDK 17 (17 or newer)
ok    Scala 3.9.0 (3.9.x)
FIX   default charset US-ASCII: use JDK 18 or newer, or set the locale to UTF-8
1 of 3 checks failed.
[exit status 1]

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 check_session.py

The second run fails one check. Before JDK 18, the JDK chose its default charset from the system: JEP 400 notes that macOS used UTF-8 unless the POSIX C locale was set, and Windows usually used a code page such as windows-1252. Since JDK 18 the default is UTF-8 everywhere. So the fix is a newer JDK, or a UTF-8 locale for JDK 17. Your run should end with Ready for this track.

Then test the five ideas of the lesson, from the three pieces to the editor’s connection file. Each answer is explained the moment you give it:

Practice that adapts to you

Practise this lesson

13 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 13 Why is the JDK the first piece to install?

    Choose one answer.

    Show the answer: Question 1 of 13

    Answer: Scala CLI and every program it builds run on a JVM, which the JDK provides

    Everything else runs on the JDK's JVM: the scala launcher, the compiler and your programs. From Scala 3.8 on it must be JDK 17 or newer.

  2. Question 2 of 13 The first scala run of a new project takes much longer than the runs after it. Why?

    Choose one answer.

    Show the answer: Question 2 of 13

    Answer: Scala CLI downloads the compiler and libraries the project needs the first time, then reuses its cache

    The first time a project asks for a Scala version or a library, Scala CLI fetches it from Maven Central into its cache. A computer without internet can run only what is already there.

  3. Question 3 of 13 Which of these does cs setup install? Choose every one.

    Choose every answer that is right.

    Show the answer: Question 3 of 13

    Answer:

    • A JVM, when it finds none
    • The scala command
    • sbt

    cs setup installs the command-line tools, scala, scalac, sbt and scalafmt among them, adds them to PATH, and makes sure a JVM is present. Editors and their extensions are installed separately.

  4. Question 4 of 13 After the install, java -version prints openjdk version "11.0.24". What does that mean for this track?

    Choose one answer.

    Show the answer: Question 4 of 13

    Answer: An older JDK comes first on PATH, and Scala 3.9 needs 17 or newer, so install a newer one and put it first

    Run cs setup again with a newer JVM, for example cs setup --jvm temurin:25, or install Temurin 25 and put its bin folder first on PATH, then open a new terminal and check again.

  5. Question 5 of 13 Which setting decides the JDK that runs the scala launcher itself?

    Choose one answer.

    Show the answer: Question 5 of 13

    Answer: The first java on PATH

    The launcher is a Java program started with the java on PATH. Your program then runs on the JDK of JAVA_HOME, or the one --java-home or //> using javaHome names.

  6. Question 6 of 13 How do you run your program on a JDK other than the one JAVA_HOME names, for one run only?

    Choose one answer.

    Show the answer: Question 6 of 13

    Answer: scala run . --java-home <the JDK's folder>

    --java-home overrides JAVA_HOME for one command. For a project, //> using javaHome does the same from the sources, and --jvm or //> using jvm asks Coursier to fetch a JVM.

  7. Question 7 of 13 You set JDK_JAVA_OPTIONS to the two options that JEP 498 and JEP 472 name. What does a scala run on JDK 25 print instead of the eight warnings?

    Choose one answer.

    Show the answer: Question 7 of 13

    Answer: One NOTE line from each JVM, three in the session: the launcher, the compiler and the program

    JDK_JAVA_OPTIONS is added to the command line of every java launcher, so the launcher stops warning; in return, every JVM prints a NOTE that it picked the variable up. The session showed three: the launcher's, the compiler's, which --server=false runs in a JVM of its own, and the program's.

  8. Question 8 of 13 Which line pins the compiler of a Scala CLI project to 3.9.0?

    Choose one answer.

    Show the answer: Question 8 of 13

    Answer: //> using scala 3.9.0

    Directives start with //> using. //> using scala 3.9.0 in project.scala pins the compiler for the whole project.

  9. Question 9 of 13 Why does this track pin 3.9.0 rather than writing //> using scala lts?

    Choose one answer.

    Show the answer: Question 9 of 13

    Answer: An exact pin gives the same compiler everywhere until you change it, while lts moves to each new LTS release

    Both forms work. The exact pin keeps every computer and CI on the same compiler until someone edits the line, which is what recorded runs and teams need.

  10. Question 10 of 13 How do you compile one run with Scala 3.9.0 without editing any file?

    Choose one answer.

    Show the answer: Question 10 of 13

    Answer: scala run . --scala 3.9.0

    The command-line option --scala 3.9.0 does for one run what //> using scala 3.9.0 does for the project.

  11. Question 11 of 13 What is BSP?

    Choose one answer.

    Show the answer: Question 11 of 13

    Answer: The Build Server Protocol, which an editor uses to ask a build tool to compile a project and report errors

    Editors do not compile Scala themselves. Through BSP they ask a build tool, here Scala CLI, to compile and to report diagnostics; .bsp/scala.json tells them how to start it.

  12. Question 12 of 13 IntelliJ IDEA's import dialog offers no BSP option for your Scala CLI folder. What is missing?

    Choose one answer.

    Show the answer: Question 12 of 13

    Answer: The .bsp folder; run scala setup-ide . in the project folder

    The .bsp folder holds the file that tells an editor how to start Scala CLI as a build server. setup-ide writes it, and so does the project's first run, compile or test.

  13. Question 13 of 13 Metals runs on a JDK of its own. Which JDK compiles your project in VS Code when you change no setting?

    Choose one answer.

    Show the answer: Question 13 of 13

    Answer: The JDK of JAVA_HOME

    The Metals documentation separates two JDKs: the one Metals runs on (17 by default) and the project's, which comes from JAVA_HOME or the Java Home setting.

Interview questions

Fresher · Why does Scala need a JDK, and which version does Scala 3.9 need?

Scala compiles to JVM bytecode, so a JVM must run every Scala program, and the Scala tools themselves are JVM programs: the compiler, and the scala command, which is Scala CLI, run on a JVM too. A JDK provides that JVM plus tools such as javap. From Scala 3.8 on, which includes 3.9 LTS, compiling and running Scala need JDK 17 or newer; the compiled classes have class-file version 61, the version of Java SE 17, so an older JVM refuses them. JDK 21 and 25 work as well. Any vendor’s build of the right version runs the same programs; this track uses Eclipse Temurin 25. Of the lines still maintained, only the older 3.3 LTS still produces bytecode for JDK 8.

Key takeaways

  • A JDK 17 or newer runs everything, the scala command is Scala CLI and fetches compilers and libraries on first use, and an editor reads the project through BSP.
  • cs setup installs the scala command and its tools; check them in a new terminal with scala version and java -version.
  • The launcher runs on the first java on PATH, and your program on the JDK of JAVA_HOME unless --java-home or a directive names another; JDK 25’s WARNING lines come from the launcher.
  • //> using scala 3.9.0 in project.scala pins the compiler for a project, and a version that is not in the cache needs one download, which fails with a misleading message when offline.
  • scala setup-ide . writes .bsp/scala.json, which VS Code with Metals and IntelliJ IDEA read to open the project.

Terms in this lesson

BSP
The Build Server Protocol: the way an editor such as VS Code with Metals or IntelliJ IDEA asks a build tool to compile a project and report its errors.
Coursier
The artifact fetcher and installer of the Scala world. cs setup installs the scala command and its tools, and cs java installs and selects JDKs.
JAVA_HOME
An environment variable that names the folder of a JDK, the one that holds bin. Scala CLI runs your program on the JDK it names unless --java-home names another.
JDK
The Java Development Kit: a JVM together with the Java compiler and tools such as javap. Scala 3.8 and later need JDK 17 or newer to compile and run.
Scala CLI
The tool behind the scala command since Scala 3.5. It fetches compilers and libraries, compiles, runs, tests and packages code, and opens the REPL.
using directive
A comment line that starts with //> using and configures Scala CLI from inside the sources, for example //> using scala 3.9.0 to pin the compiler version.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.