Kotlin Module 1 – Start here: Kotlin 2.4 on the JVM
Kotlin scripts, the REPL and notebooks
Run .kts and .main.kts scripts with arguments and Maven libraries, see what the REPL does in Kotlin 2.4.20, and choose a tool for quick experiments.
What you will learn
- Run a .kts script with kotlinc -script and pass arguments to it
- Add a library to a .main.kts script with @file:DependsOn, and predict when it needs the network
- Choose between scripts, the REPL, scratch files, the Playground and Kotlin Notebook for a quick experiment
Before you start
What you’ll learn · Before you start
What you will learn
- Run a .kts script with kotlinc -script and pass arguments to it
- Add a library to a .main.kts script with @file:DependsOn, and predict when it needs the network
- Choose between scripts, the REPL, scratch files, the Playground and Kotlin Notebook for a quick experiment
Before you start
On this page
A Kotlin script needs no main, no project and no build: total.main.kts, 21 lines, totals a file of expenses with
one command. The same command ran a second script that fetched a library from Maven Central on its first run, and
failed with exit status 1 when the network was off and nothing was cached. And kotlinc -Xrepl, the command-line
REPL, did not start at all in Kotlin 2.4.20.
This lesson runs scripts with arguments, pulls a library into a script and watches when that needs the network, looks at what is left of the REPL, and chooses between scripts, scratch files, the Playground and notebooks for a quick experiment.
A script runs from top to bottom, with its arguments in args
A Kotlin script: A .kts file of top-level statements that run from top to bottom, with the command-line arguments in args. kotlinc -script and kotlinr run it without a main function. is a file ending in .kts whose statements sit at the top level. They
run in order, and the words given after the script’s name arrive in args, as in main. This one totals a CSV file
of items and their prices in paise, and skips the lines it cannot read:
// Totals the expenses in a CSV file of item,paise lines, named as the first argument.
import java.io.File
if (args.isEmpty()) {
println("usage: kotlinc -script total.main.kts -- <file.csv>")
kotlin.system.exitProcess(2)
}
var total = 0L
var items = 0
File(args[0]).readLines().drop(1).forEachIndexed { index, line ->
val paise = line.substringAfter(",").toLongOrNull()
if (paise == null) {
println("skipped line ${index + 2}: $line")
} else {
total += paise
items++
}
}
val rupees = "${total / 100}.${(total % 100).toString().padStart(2, '0')}"
println("$items items, total $rupees") item,paise
Tea,1250
Bus pass,30000
Lunch,8525
Books,abc
Snacks,4000 from session import run
# A .main.kts script run three ways: with kotlinc -script and its arguments after --, with kotlinr, and without the
# argument it needs.
run("""
cd expenses
kotlinc -script total.main.kts -- expenses.csv
kotlinr total.main.kts expenses.csv
!kotlinc -script total.main.kts
""") Output
$ cd expenses $ kotlinc -script total.main.kts -- expenses.csv skipped line 5: Books,abc 4 items, total 437.75 $ kotlinr total.main.kts expenses.csv skipped line 5: Books,abc 4 items, total 437.75 $ kotlinc -script total.main.kts usage: kotlinc -script total.main.kts -- <file.csv> [exit status 2]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 script_session.py
kotlinc -script total.main.kts -- expenses.csv compiled the script and ran it: -- ends the compiler’s own
options, and everything after it goes to the script. Books,abc has no number, so the script reported it and
totalled the other 4 lines, 43,775 paise, 437.75 in rupees. kotlinr, the runner, ran the same script with the
arguments straight after its name. Without the argument, the script printed its usage line and ended with the status
it chose, exitProcess(2).
Scripts suit small jobs you repeat, such as checking a file, renaming many files or trying an idea, and they live in
version control like any file. Gradle’s build files, settings.gradle.kts and build.gradle.kts, are Kotlin scripts
too, with a definition of Gradle’s own, so the same syntax and the same kinds of compiler messages apply there.
Command-line scripting is still Alpha on the components-stability page, so automation built on it may need changes
when Kotlin is upgraded.
Checkpoint
Check: Scripts and their arguments
@file:DependsOn fetches a library on first use
A script whose name ends in .main.kts script: A Kotlin script whose name ends in .main.kts. It can fetch libraries with @file:DependsOn and include other scripts with @file:Import. can name libraries at its top. The definition behind
that ending ships with the compiler, and it resolves @file:DependsOn("group:name:version") from
Maven Central: The public repository of Java and Kotlin libraries. @file:DependsOn and Gradle's mavenCentral() download libraries from it by their group, name and version. unless an @file:Repository line names another repository:
@file:DependsOn("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.11.0")
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
runBlocking {
val a = async { 2 }
val b = async { 3 }
println("sum from script = ${a.await() + b.await()}")
} The session ran it three times, first with the network off and an empty Maven cache, as on a locked-down CI machine:
from session import run
# A script that depends on a library from Maven Central. First with the network off and an empty Maven cache, then
# with the network on (network: true), which downloads the library into ~/.m2, then offline again, from that cache.
# The same file named sum.kts, a plain script, does not know @file:DependsOn.
run("""
# the network is off and ~/.m2 is empty
cd deps
!kotlinc -script sum.main.kts
""")
run("""
# the network is on
kotlinc -script sum.main.kts
ls $HOME/.m2/repository/org/jetbrains/kotlinx
""", network=True)
run("""
# the network is off again
kotlinc -script sum.main.kts
!kotlinc -script sum.kts
""") Output
# the network is off and ~/.m2 is empty
$ cd deps
$ kotlinc -script sum.main.kts
sum.main.kts:1:1: warning: file 'org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.11.0' not found
@file:DependsOn("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.11.0")
^
sum.main.kts:1:1: warning: artifactResolutionException: The following artifacts could not be resolved: org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:pom:1.11.0 (absent): Could not transfer artifact org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:pom:1.11.0 from/to maven central (https://repo.maven.apache.org/maven2/): repo.maven.apache.org: nodename nor servname provided, or not known: org.jetbrains.kotlin.org.eclipse.aether.collection.DependencyCollectionException: Failed to collect dependencies at org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:jar:1.11.0
@file:DependsOn("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.11.0")
^
[exit status 1]
# the network is on
$ kotlinc -script sum.main.kts
sum from script = 5
$ ls $HOME/.m2/repository/org/jetbrains/kotlinx
kotlinx-coroutines-bom
kotlinx-coroutines-core-jvm
# the network is off again
$ kotlinc -script sum.main.kts
sum from script = 5
$ kotlinc -script sum.kts
sum.kts:1:7: error: unresolved reference 'DependsOn'.
@file:DependsOn("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.11.0")
^
sum.kts:3:8: error: unresolved reference 'kotlinx'.
import kotlinx.coroutines.async
^
sum.kts:4:8: error: unresolved reference 'kotlinx'.
import kotlinx.coroutines.runBlocking
^
sum.kts:6:1: error: unresolved reference 'runBlocking'.
runBlocking {
^
sum.kts:7:13: error: unresolved reference 'async'.
val a = async { 2 }
^
sum.kts:8:13: error: unresolved reference 'async'.
val b = async { 3 }
^
[exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 deps_session.py
Step through what happened in each run:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- The name decides. A file ending in .main.kts gets the script definition that ships with the compiler, which knows @file:DependsOn and @file:Import. The same code named sum.kts is a plain script, and its first error was unresolved reference DependsOn.
- Look in the cache. For each @file:DependsOn, the script looks in the local Maven repository, ~/.m2/repository, and downloads what is missing from Maven Central, the default repository.
- Offline with an empty cache. With the network off and ~/.m2 empty, resolution failed. kotlinc printed two warnings, the second naming the cause, could not transfer the artifact from Maven Central, and ended with exit status 1.
- Online, once. With the network on, the library arrived in ~/.m2/repository/org/jetbrains/kotlinx, kotlinx-coroutines-core-jvm and its bill of materials, and the script went on.
- Compile. The script is compiled like any Kotlin code, with the library on its classpath. Its top-level statements become the body of a class that the scripting host creates and runs.
- Run. The statements run from top to bottom and print sum from script = 5. With the network off again, the next run found the library in ~/.m2 and printed the same.
Offline and with an empty cache, the script printed two warnings and stopped with exit status 1; the second warning
names the cause, that the library could not be transferred from Maven Central. With the network on, the library went
into the local Maven repository, ~/.m2/repository, and the script printed sum from script = 5. Offline again, the
cached library was enough. The last command ran the same code as sum.kts, a plain script, whose definition does not
know @file:DependsOn at all: six errors, starting with unresolved reference 'DependsOn'.
Key idea
A .main.kts script brings its libraries with @file:DependsOn: the first run needs the network to fill
~/.m2/repository, and every later run works offline from that cache.
Checkpoint
Check: @file:DependsOn and the Maven cache
The command-line REPL is deprecated, and did not start in 2.4.20
The REPL: Read-eval-print loop: a prompt that compiles and runs each line you type and prints the result. kotlinc's own REPL is deprecated in Kotlin 2.4., short for read-eval-print loop, was the classic way to try a line of Kotlin: type an expression, see its value. In Kotlin 2.4.20 its command-line form is on the way out:
from session import run
# The command-line REPL of Kotlin 2.4.20: kotlinc alone, kotlinc -Xrepl, and how kotlinc -X describes the option.
run("""
!kotlinc
!kotlinc -Xrepl
kotlinc -X 2>&1 | grep -- -Xrepl
""") Output
$ kotlinc error: Kotlin REPL is deprecated and should be enabled explicitly for now; please use the '-Xrepl' option [exit status 1] $ kotlinc -Xrepl error: unable to run REPL, no scripting plugin loaded [exit status 1] $ kotlinc -X 2>&1 | grep -- -Xrepl -Xrepl Run Kotlin REPL (deprecated)
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 repl_session.py
kotlinc alone used to open the REPL; now it stops with Kotlin REPL is deprecated and should be enabled explicitly for now. kotlinc -Xrepl, the option that message suggests, stopped with unable to run REPL, no scripting plugin loaded, and kotlinc -X describes the option as Run Kotlin REPL (deprecated). So do not build habits or scripts on
it.
The REPL in Kotlin 2.4.20
The Kotlin command-line page and the compiler reference still describe kotlinc -Xrepl as the way to open the REPL.
With kotlinc 2.4.20 it did not start, so check kotlinc -X on the version you have before you rely on it.
What replaces it depends on where you work. In a terminal, the Kotlin documentation lists the ki shell, the Kotlin
Interactive Shell, a separate tool installed with brew install ki on macOS. In IntelliJ IDEA and Android Studio, a
scratch file: A file outside your project, in IntelliJ IDEA or Android Studio, where Kotlin code runs as if it were inside main and each result appears beside its line. runs Kotlin as if it were inside main, with an interactive mode that
runs it whenever you stop typing. In a browser, Kotlin Playground runs code with several Kotlin versions.
Checkpoint
Check: The command-line REPL in Kotlin 2.4
Choose a tool by what the experiment needs
Each way of running a little Kotlin keeps something different and reaches different libraries. A
notebook: A document of code cells, text cells and their outputs, saved as an .ipynb file in the Jupyter format. Kotlin Notebook is the IntelliJ IDEA plugin that runs Kotlin cells. mixes code cells, text and outputs, charts included, in an .ipynb file in the
Jupyter format; Kotlin Notebook is the IntelliJ IDEA plugin that runs Kotlin cells, built on the Kotlin kernel for
Jupyter, and it suits exploring data with libraries such as Kotlin DataFrame.
Kotlin Notebook from IntelliJ IDEA 2026.2
From IntelliJ IDEA 2026.2 on, JetBrains no longer ships Kotlin Notebook with the IDE and no longer supports it officially; its code remains public on GitHub. The files keep the Jupyter format, so they still open in other notebook viewers, GitHub among them.
Kotlin Playground sends your code to a compile server that compiles and runs it, which is handy for sharing a snippet and wrong for code that must stay private. Answer the three questions about your experiment:
Answer the questions above: the table then marks what fits your case and says why the rest does not.
| Tool | State in Kotlin 2.4 | Keeps your work | Libraries |
|---|---|---|---|
| A .kts script Runs: kotlinc -script, on your computer | Mixed ran; command-line scripting is Alpha | Good a file you can run again, in CI too | Poor none beyond the standard library and the JDK |
| A .main.kts script Runs: kotlinc -script, with @file:DependsOn | Mixed ran; command-line scripting is Alpha | Good a file you can run again, in CI too | Good @file:DependsOn, from Maven Central, cached in ~/.m2 |
| A scratch file Runs: inside IntelliJ IDEA or Android Studio | Good part of the IDE: code runs as if inside main | Mixed a file outside the project | Good a project module's classes, when you choose its classpath |
| Kotlin Notebook Runs: cells in IntelliJ IDEA, as .ipynb files | Poor not bundled with IntelliJ IDEA from 2026.2 on | Good cells, text and outputs in one .ipynb file | Good added from inside the notebook |
| Kotlin Playground Runs: in a browser, compiled on a server | Good several Kotlin versions, nothing to install | Mixed a shareable link; the code leaves your computer | Poor only what the Playground's server offers |
| kotlinc -Xrepl Runs: a prompt in the terminal | Poor deprecated; it did not start in 2.4.20 | Poor nothing after you quit | Poor not reachable when it does not start |
How the answers decide
- “Will you run it again later, or in CI?” Yes, it should stay: rules out kotlinc -Xrepl and Kotlin Playground and adds a caveat to A scratch file and Kotlin Notebook. A file in the project or in CI is repeatable; the REPL keeps nothing, and the Playground and IDE tools do not run in CI.
- “Does it need a library from Maven Central?” Yes, a library: rules out A .kts script and kotlinc -Xrepl and adds a caveat to Kotlin Playground. A plain .kts script does not know @file:DependsOn, which only .main.kts scripts have; the Playground offers only its own set.
- “Must the code stay on your computer?” Yes, it is private: rules out Kotlin Playground. The Playground sends the code to a server that compiles and runs it, so the code leaves your computer.
- “Will you run it again later, or in CI?” No, a one-off try: adds a caveat to Kotlin Notebook. Kotlin Notebook is no longer bundled with IntelliJ IDEA from 2026.2 on, so a scratch file or the Playground is the quicker way to try something.
In short: a .main.kts script for anything you will run again or that needs a library, a scratch file for a quick try
inside your project, the Playground for a shareable snippet that may leave your computer, and a notebook only where
your IDE still has it. The command-line REPL is no longer an option in 2.4.20.
Checkpoint
Check: Choosing a tool for an experiment
Practise: total a CSV in rupees
The program below checks a function for a script like total.main.kts, with amounts in rupees that may have two
decimals, such as 12.50. It should return the total in paise and skip lines that do not parse. As given, it returns
0, so only the check of the empty list passes, and the program ends with status 1:
// Practice: total "name,amount" lines, where an amount is rupees with up to two decimals, in paise.
// Lines that do not parse are skipped.
fun sumCsv(lines: List<String>): Long {
// Replace this line with the parsing the lesson describes.
return 0L
}
fun main() {
val cases =
listOf(
listOf("Tea,12.50", "Bus,30", "Lunch,85.25") to 12775L,
listOf("Tea,12.50", "Books,abc", "Snacks,40") to 5250L,
emptyList<String>() to 0L,
)
var failed = 0
for ((lines, expected) in cases) {
val actual = sumCsv(lines)
if (actual != expected) failed++
val mark = if (actual == expected) "ok " else "FAIL"
println("$mark $lines -> $actual paise (expected $expected)")
}
println("${cases.size - failed} of ${cases.size} checks pass")
if (failed > 0) kotlin.system.exitProcess(1)
} Output (exit status 1)
FAIL [Tea,12.50, Bus,30, Lunch,85.25] -> 0 paise (expected 12775) FAIL [Tea,12.50, Books,abc, Snacks,40] -> 0 paise (expected 5250) ok [] -> 0 paise (expected 0) 1 of 3 checks pass
Recorded with Kotlin 2.4.20 on OpenJDK 25.0.4.1 LTS (Eclipse Temurin) on macOS 26 arm64. To run it yourself: kotlinc SumCsv.kt -include-runtime -d main.jar && java -jar main.jar
Write sumCsv in a copy of the file, then compile and run it with kotlinc SumCsv.kt -include-runtime -d sumcsv.jar && java -jar sumcsv.jar; once the three checks pass, call it from a .main.kts script that reads the file named in
args. First hint: split the amount with split("."): one part means whole rupees, and two parts mean rupees and
paise, which you pad on the right to two digits, so that 12.5 means 1,250 paise. toLongOrNull() gives null for a
part that is not a number, and such a line is skipped.
Ten questions close the module: scripts, script libraries, the REPL and choosing a tool. The first one comes from the idea you are least sure of, and each answer is explained on the spot:
Practice that adapts to you
Practise this lesson
10 questions about 4 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Interview questions
Fresher · What is the difference between a .kts and a .main.kts script?
Both are Kotlin scripts: top-level statements that run from top to bottom, with the command-line arguments in args,
run with kotlinc -script or kotlinr. The difference is the script definition the file’s name selects. A file
ending in .main.kts gets the definition that ships with the Kotlin compiler, which adds @file:DependsOn to fetch
libraries by their Maven coordinates, from Maven Central by default or from a repository named with
@file:Repository, and @file:Import to include another script. A plain .kts file has none of that: the same code
fails with unresolved reference 'DependsOn'. The libraries are cached in ~/.m2/repository, so the first run needs
the network and later runs do not.
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Fresher to mid
A .main.kts release script with @file:DependsOn works on every laptop but fails in a new CI image with exit status 1 and two warnings, the second about an artifact it could not transfer from Maven Central. Explain the failure and two fixes, with their costs.
-
Mid to senior
A 60-line .main.kts script that renames files is used by five people and has started to grow. When would you keep it as a script, and when would you turn it into a small Gradle project with tests? Give the signals you would watch.
Key takeaways
- A
.ktsscript runs its top-level statements in order with the words after--inargs;kotlinc -scriptandkotlinrboth run it, and anexitProcesscall sets its exit status. - A
.main.ktsscript fetches libraries with@file:DependsOnfrom Maven Central into~/.m2/repository: offline with an empty cache it failed with status 1, online it ran, and offline again it ran from the cache. - A plain
.ktsfile does not know@file:DependsOn; the ending.main.ktsselects the definition that does. - The command-line REPL is deprecated:
kotlincrefused to open it andkotlinc -Xrepldid not start in Kotlin 2.4.20, so use scratch files, the ki shell or the Playground instead. - Choose by what the experiment needs: a script to repeat it, a scratch file to try project code, the Playground for shareable snippets that may leave your computer, and a notebook only where the IDE still bundles it.
Terms in this lesson
- .main.kts script
- A Kotlin script whose name ends in .main.kts. It can fetch libraries with @file:DependsOn and include other scripts with @file:Import.
- Kotlin script
- A .kts file of top-level statements that run from top to bottom, with the command-line arguments in args. kotlinc -script and kotlinr run it without a main function.
- Maven Central
- The public repository of Java and Kotlin libraries. @file:DependsOn and Gradle's mavenCentral() download libraries from it by their group, name and version.
- notebook
- A document of code cells, text cells and their outputs, saved as an .ipynb file in the Jupyter format. Kotlin Notebook is the IntelliJ IDEA plugin that runs Kotlin cells.
- REPL
- Read-eval-print loop: a prompt that compiles and runs each line you type and prints the result. kotlinc's own REPL is deprecated in Kotlin 2.4.
- scratch file
- A file outside your project, in IntelliJ IDEA or Android Studio, where Kotlin code runs as if it were inside main and each result appears beside its line.
References
- Kotlin command-line compiler (run scripts and the REPL) (JetBrains (kotlinlang.org))
- Kotlin compiler options (-script and -Xrepl) (JetBrains (kotlinlang.org))
- KEEP-0075: Kotlin scripting support (the main.kts definition, @DependsOn, @Repository and @Import) (JetBrains (Kotlin KEEP))
- Get started with Kotlin custom scripting (JetBrains (kotlinlang.org))
- Stability of Kotlin components (scripting) (JetBrains (kotlinlang.org))
- Run code snippets (scratch files, Kotlin Playground, the ki shell) (JetBrains (kotlinlang.org))
- Run Kotlin interactively (scratch files and their module classpath) (JetBrains (IntelliJ IDEA documentation))
- Kotlin Notebook overview (JetBrains (kotlinlang.org))
- Kotlin compiler server (the API behind Kotlin Playground) (JetBrains (GitHub))
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress