Kotlin Module 1 – Start here: Kotlin 2.4 on the JVM
Read compiler errors, warnings and opt-ins
Read a Kotlin diagnostic by file, line, column and severity, fix errors before warnings, satisfy opt-in and preview errors, and fail builds on warnings.
What you will learn
- Read a K2 diagnostic (file, line, column, severity, message) and find its cause
- Distinguish errors, warnings, opt-in requirements and experimental-feature errors
- Configure a build to fail on warnings with -Werror or allWarningsAsErrors
- Fix a file's diagnostics in an order that brings its hidden warnings to light
Before you start
What you’ll learn · Before you start
What you will learn
- Read a K2 diagnostic (file, line, column, severity, message) and find its cause
- Distinguish errors, warnings, opt-in requirements and experimental-feature errors
- Configure a build to fail on warnings with -Werror or allWarningsAsErrors
- Fix a file's diagnostics in an order that brings its hidden warnings to light
Before you start
On this page
kotlinc reported three errors in a ten-line file, and stayed silent about a fourth problem, a comparison that
compiles and gives the wrong answer. That one appeared only after the three errors were fixed, as a warning, and the
program then printed false where the right answer was true.
This lesson reads compiler messages from Kotlin 2.4.20 part by part, fixes a file round by round in the order that
brings such hidden warnings to light, tells errors, warnings, opt-in requirements and experimental-feature errors
apart, and makes a build fail on every warning, both with kotlinc and with Gradle.
A diagnostic says where, how bad and what
Here is a short program with three mistakes in it, of the kind every beginner types:
fun total(prices: List<Int>): Int = prices.sum()
fun main() {
val prices = listOf(120, 80, 45)
val count: Int = "3"
println("Total: ${totl(prices)}")
println("All different: ${prices.allDistinct()}")
val paid = intArrayOf(120, 80, 45)
println("Same as paid: ${paid == prices.toIntArray()}")
} Compiled, each mistake became a diagnostic: A message from the compiler about the code: the file, line and column, a severity such as error or warning, and the text, followed by the source line with carets under the place. of the K2 compiler: The Kotlin compiler that became stable in Kotlin 2.0.0. It checks the code and reports the diagnostics of Kotlin 2.x., the compiler that became stable in Kotlin 2.0.0. The second run adds an option that the next section explains:
from session import run
# The broken Receipt.kt compiled twice: as kotlinc reports it, and with -Xreport-all-warnings, which also prints the
# warnings that kotlinc leaves out while a file has errors.
run("""
cd receipt
!kotlinc Receipt.kt -d out
!kotlinc Receipt.kt -Xreport-all-warnings -d out
""") Output
$ cd receipt
$ kotlinc Receipt.kt -d out
Receipt.kt:5:20: error: initializer type mismatch: expected 'Int', actual 'String'.
val count: Int = "3"
^
Receipt.kt:6:23: error: unresolved reference 'totl'.
println("Total: ${totl(prices)}")
^^^^
Receipt.kt:7:38: error: this declaration needs opt-in. Its usage must be marked with '@kotlin.ExperimentalStdlibApi' or '@OptIn(kotlin.ExperimentalStdlibApi::class)'
println("All different: ${prices.allDistinct()}")
^^^^^^^^^^^
[exit status 1]
$ kotlinc Receipt.kt -Xreport-all-warnings -d out
Receipt.kt:5:20: error: initializer type mismatch: expected 'Int', actual 'String'.
val count: Int = "3"
^
Receipt.kt:6:23: error: unresolved reference 'totl'.
println("Total: ${totl(prices)}")
^^^^
Receipt.kt:7:38: error: this declaration needs opt-in. Its usage must be marked with '@kotlin.ExperimentalStdlibApi' or '@OptIn(kotlin.ExperimentalStdlibApi::class)'
println("All different: ${prices.allDistinct()}")
^^^^^^^^^^^
Receipt.kt:9:35: warning: '==' on arrays compares only references. Replace '==' with 'contentEquals' to compare the arrays' contents or use '===' to remove the warning.
println("Same as paid: ${paid == prices.toIntArray()}")
^^
[exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 errors_session.py
Every diagnostic has the same five parts. Take the first one apart:
| Part | In the first diagnostic | What it tells you |
|---|---|---|
| File | Receipt.kt |
which file to open |
| Line and column | 5:20 |
line 5, the 20th character, where the caret points |
| Severity | error |
an error stops the build; a warning does not |
| Message | initializer type mismatch: expected 'Int', actual 'String'. |
what the compiler expected and what it found |
| Source and caret | val count: Int = "3" with ^ under the = |
the exact place, for a quick look |
Read the message for what it compares: here count is declared as an Int, and the value given to it is the text
"3". The second error, unresolved reference 'totl', means the compiler knows no function of that name; it is a
typing slip for total. The third names its own fix, which the section after next looks at.
Checkpoint
Check: The parts of a diagnostic
Errors first: warnings wait until the errors are gone
The second run in the session added -Xreport-all-warnings, and a fourth diagnostic appeared: a warning on line 9
about == on arrays. Without that option, kotlinc 2.4.20 printed no warnings at all while the file had errors.
Warnings beside errors in kotlinc 2.4.20
This is how kotlinc 2.4.20 reports: while a file has errors it prints only the errors, and
-Xreport-all-warnings adds the warnings to them. Check what your own version prints before you rely on either.
So a file that compiles at last can still show new messages, the warnings that were waiting. Fix in rounds, and step through them:
The drawing shows the whole scene; its 6 steps are listed below.
Every step as text
- Round one. kotlinc printed three errors, each with its file, line and column, and wrote no classes. The array warning on line 9 was not among them, because kotlinc leaves warnings out while a file has errors.
- The first error first. Line 5, column 20: initializer type mismatch, expected 'Int', actual 'String'. count is declared as an Int, so it gets 3 instead of the text "3".
- Then the other two. Line 6: unresolved reference 'totl', a typing slip for total. Line 7: allDistinct() needs opt-in, so main gets @OptIn(ExperimentalStdlibApi::class), the fix the message itself names.
- Round two. With the errors gone, the warning appears: '==' on arrays compares only references. A warning does not stop the build, so the classes are written.
- The bug that compiled. The program prints "Same as paid: false", although both arrays hold 120, 80 and 45. == asked whether they are the same array object, and they are two.
- Fix, then lock it in. contentEquals compares what the arrays hold, so the program prints true. Compiled with -Werror, the file passes, and from now on any new warning fails the build.
Start each round at the first error: a single slip can cause several messages after it, and some disappear with the first fix. After the third error, the file compiled with one warning. This session runs it, and it shows why a warning deserves the same attention as an error:
fun total(prices: List<Int>): Int = prices.sum()
@OptIn(ExperimentalStdlibApi::class)
fun main() {
val prices = listOf(120, 80, 45)
val count: Int = 3
println("Total: ${total(prices)}")
println("All different: ${prices.allDistinct()}")
val paid = intArrayOf(120, 80, 45)
println("Same as paid: ${paid == prices.toIntArray()}")
} from session import run
# Receipt.kt with its three errors fixed: it compiles with one warning and prints a wrong answer. -Werror turns the
# warning into a failure that writes no classes, and -Wextra adds checks that are off by default. The clean version
# passes -Werror and prints the right answer.
run("""
cd fixed
kotlinc Receipt.kt -d out
kotlinr -cp out ReceiptKt
!kotlinc Receipt.kt -Werror -d strict
!ls strict
kotlinc Receipt.kt -Wextra -d extra
cd ../clean
kotlinc Receipt.kt -Werror -d out
kotlinr -cp out ReceiptKt
""") Output
$ cd fixed
$ kotlinc Receipt.kt -d out
Receipt.kt:10:35: warning: '==' on arrays compares only references. Replace '==' with 'contentEquals' to compare the arrays' contents or use '===' to remove the warning.
println("Same as paid: ${paid == prices.toIntArray()}")
^^
$ kotlinr -cp out ReceiptKt
Total: 245
All different: true
Same as paid: false
$ kotlinc Receipt.kt -Werror -d strict
error: warnings found and -Werror specified
Receipt.kt:10:35: warning: '==' on arrays compares only references. Replace '==' with 'contentEquals' to compare the arrays' contents or use '===' to remove the warning.
println("Same as paid: ${paid == prices.toIntArray()}")
^^
[exit status 1]
$ ls strict
ls: strict: No such file or directory
[exit status 1]
$ kotlinc Receipt.kt -Wextra -d extra
Receipt.kt:6:9: warning: variable is unused.
val count: Int = 3
^^^^^
Receipt.kt:10:35: warning: '==' on arrays compares only references. Replace '==' with 'contentEquals' to compare the arrays' contents or use '===' to remove the warning.
println("Same as paid: ${paid == prices.toIntArray()}")
^^
$ cd ../clean
$ kotlinc Receipt.kt -Werror -d out
$ kotlinr -cp out ReceiptKt
Total: 245 for 3 items
All different: true
Same as paid: true
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 warnings_session.py
The fixed file compiled and printed Same as paid: false, although both arrays hold 120, 80 and 45: on arrays, ==
asks whether two names refer to the same array object, and contentEquals compares what they hold, as the clean
version does.
Key idea
Fix the first error first and compile again: errors can hide each other, and warnings appear only once the errors are gone, so expect new messages in the last round, after the last error.
Checkpoint
Check: Errors first, then the hidden warnings
Opt-in and preview errors name their own fix
Two kinds of error are not mistakes in your code at all, but rules about what you may use. The third error of
Receipt.kt was an opt-in requirement: A mark on an API, such as @ExperimentalStdlibApi, that makes code using it fail to compile, or warn, until the code accepts it with @OptIn or the -opt-in compiler option.: allDistinct() is marked with
@ExperimentalStdlibApi in Kotlin 2.4.20, an API that may still change, and the compiler accepts its use only once
the code says it accepts that risk. The message names two ways: mark the use with
@OptIn(kotlin.ExperimentalStdlibApi::class), as the fixed file does on main, or mark the calling declaration
itself with @kotlin.ExperimentalStdlibApi, which passes the requirement on to everything that calls it. A third way
opts in a whole module: the compiler option -opt-in=kotlin.ExperimentalStdlibApi, which has the same effect as the
annotation on every declaration. An opt-in requirement can also be a warning instead of an error, when its author sets
the level to WARNING; the default level is ERROR.
An experimental language feature: A language feature that compiles only with an -X compiler option that names it, such as -Xcollection-literals, and comes with no stability guarantee. is the other kind: new syntax that compiles only when you switch it on. Collection literals, square brackets for a list, are one in Kotlin 2.4.20:
fun main() {
val fees: List<Int> = [120, 80, 45]
println(fees.sum())
} from session import run
# A collection literal, a preview feature in Kotlin 2.4.20: three errors without its flag, and a program that runs
# with it.
run("""
cd preview
!kotlinc Fees.kt -d out
kotlinc Fees.kt -Xcollection-literals -d out
kotlinr -cp out FeesKt
""") Output
$ cd preview
$ kotlinc Fees.kt -d out
Fees.kt:2:25: error: initializer type mismatch: expected 'List<Int>', actual 'Array<Int>'.
val fees: List<Int> = [120, 80, 45]
^
Fees.kt:2:27: error: array literals outside of annotations are unsupported.
val fees: List<Int> = [120, 80, 45]
^^^^^^^^^^^^^
Fees.kt:2:27: error: the feature "collection literals" is experimental and should be enabled explicitly. This can be done by supplying the compiler argument '-Xcollection-literals', but note that no stability guarantees are provided.
val fees: List<Int> = [120, 80, 45]
^^^^^^^^^^^^^
[exit status 1]
$ kotlinc Fees.kt -Xcollection-literals -d out
$ kotlinr -cp out FeesKt
245
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 preview_session.py
The three errors all come from the brackets on line 2, and only the third says what is wrong: the feature
“collection literals” is experimental, and -Xcollection-literals enables it, “but note that no stability guarantees
are provided”. With the flag the program ran and printed 245. The flag itself is one of the -X options, which the
compiler reference calls unstable: a later release may rename one or change what it does.
Switching on a preview feature to silence an error
Adding -Xcollection-literals to a production build makes the error go away, and ties the code to syntax that a
later Kotlin release may change or drop. Write listOf(120, 80, 45) instead, and keep preview flags in experiments.
Checkpoint
Check: Opt-in and experimental-feature errors
A strict build turns every warning into a failure
The session above shows the failure. With -Werror: The kotlinc option that treats every warning as an error, so a build with any warning fails and writes no class files. In Gradle the same setting is allWarningsAsErrors., kotlinc reported error: warnings found and -Werror specified, ended with exit status 1 and wrote no classes, so the wrong false could never ship. The clean version
passed -Werror and printed true. -Wextra adds checks that are off by default, such as variable is unused for
count. A build on a CI server should fail on warnings, because nobody reads the warnings of a green build.
-progressive is a different switch. It makes the compiler apply new deprecations, and fixes to unstable behaviour,
in the release that brings them, with no grace period. Code that compiles in progressive mode also compiles without
it, while older code may stop compiling once it is on, so it suits new code better than a large old codebase switched
over in one step.
In a Gradle build the switch that matches -Werror is allWarningsAsErrors, one of the Kotlin compiler options:
plugins {
kotlin("jvm") version "2.4.20"
}
repositories {
mavenCentral()
}
kotlin {
jvmToolchain(25)
compilerOptions {
allWarningsAsErrors = true
optIn.add("kotlin.ExperimentalStdlibApi")
}
} optIn.add("kotlin.ExperimentalStdlibApi") is the module-wide opt-in, so the source needs no @OptIn. The source
still compares two arrays with ==. The session builds it twice: first a copy in lenient/ whose build file lacks
the allWarningsAsErrors line, then the strict project as shown:
from session import run
# network: true. The same array warning in two Gradle builds of one project: lenient/ has no allWarningsAsErrors, so
# the warning is printed and the build passes; strict/ sets allWarningsAsErrors = true, and the compile task fails.
# The opt-in is given for the whole module by compilerOptions.optIn, so allDistinct() needs no @OptIn.
run("""
cd lenient
gradle build
cd ../strict
!gradle build
""", network=True) Output
$ cd lenient
$ gradle build
> Task :checkKotlinGradlePluginConfigurationErrors SKIPPED
> Task :processResources NO-SOURCE
> Task :processTestResources NO-SOURCE
> Task :compileKotlin
w: src/main/kotlin/Receipt.kt:5:35 '==' on arrays compares only references. Replace '==' with 'contentEquals' to compare the arrays' contents or use '===' to remove the warning.
> Task :compileJava NO-SOURCE
> Task :classes UP-TO-DATE
> Task :jar
> Task :assemble
> Task :compileTestKotlin NO-SOURCE
> Task :compileTestJava NO-SOURCE
> Task :testClasses UP-TO-DATE
> Task :test NO-SOURCE
> Task :check UP-TO-DATE
> Task :build
[Incubating] Problems report is available at: build/reports/problems/problems-report.html
BUILD SUCCESSFUL in …
2 actionable tasks: 2 executed
Consider enabling configuration cache to speed up this build: https://docs.gradle.org/9.7.1/userguide/configuration_cache_enabling.html
$ cd ../strict
$ gradle build
> Task :checkKotlinGradlePluginConfigurationErrors SKIPPED
> Task :processResources NO-SOURCE
> Task :processTestResources NO-SOURCE
> Task :compileKotlin
e: warnings found and -Werror specified
e: src/main/kotlin/Receipt.kt:5:35 '==' on arrays compares only references. Replace '==' with 'contentEquals' to compare the arrays' contents or use '===' to remove the warning.
> Task :compileKotlin FAILED
[Incubating] Problems report is available at: build/reports/problems/problems-report.html
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':compileKotlin' (registered by plugin 'org.jetbrains.kotlin.jvm').
> A failure occurred while executing org.jetbrains.kotlin.compilerRunner.btapi.BuildToolsApiCompilationWork
> Kotlin compiler error
Kotlin compiler error
warnings found and -Werror specified
* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights from a Build Scan (powered by Develocity).
> Get more help at https://help.gradle.org.
BUILD FAILED in …
1 actionable task: 1 executed
[exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 gradle_session.py
Gradle prints the compiler’s diagnostics in a format of its own: a letter for the severity first, w: for a warning
and e: for an error, then the file with its line and column. On your computer the file appears as a full file://
address; the session shows paths from the project’s folder. The lenient build printed the warning and passed. In the
strict one the same warning became an error, compileKotlin failed and the build stopped.
Checkpoint
Check: Failing a build on warnings
Practise: make the kata compile
The file below has two errors and one problem that waits behind them. Compiled with -Werror as given, it shows the
two errors:
// Practice: make this file compile with kotlinc Kata.kt -Werror and print the three lines its comments describe.
fun average(marks: List<Int>): Double = marks.sum().toDouble() / marks.size
fun main() {
val marks = listOf(72, 90, 39)
// Average: 67.0
println("Average: ${avrage(marks)}")
// Highest: 90
val highest: Int = marks.maxOrNull()
println("Highest: $highest")
// Same as last term: true
val lastTerm = intArrayOf(72, 90, 39)
println("Same as last term: ${lastTerm == marks.toIntArray()}")
} from session import run
# The practice file as it is given, compiled with -Werror.
run("""
cd kata
!kotlinc Kata.kt -Werror -d out
""") Output
$ cd kata
$ kotlinc Kata.kt -Werror -d out
Kata.kt:7:25: error: unresolved reference 'avrage'.
println("Average: ${avrage(marks)}")
^^^^^^
Kata.kt:9:22: error: initializer type mismatch: expected 'Int', actual 'Int?'.
val highest: Int = marks.maxOrNull()
^
[exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 kata_session.py
Copy it and fix it round by round until kotlinc Kata.kt -Werror -include-runtime -d kata.jar && java -jar kata.jar
prints the three lines its comments describe. First hint: maxOrNull() returns Int?, because a list can be empty,
so highest either becomes Int? or gets a value for the empty case, such as ?: 0. After the two errors, compile
again before you run it.
To finish, eight questions on reading diagnostics, fixing order, opt-ins and strict builds. The idea you know least is asked first, and every answer carries its explanation:
Practice that adapts to you
Practise this lesson
8 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 an error and a warning from the Kotlin compiler?
An error means the compiler cannot or will not build the code: a type mismatch, an unresolved name, or the use of an
opt-in API without opting in. A build with an error writes no class files and ends with exit status 1. A warning
means the code compiles but probably does something you did not mean, such as == on two arrays, which compares the
array objects rather than their contents; the class files are written and the program runs. In Kotlin 2.4.20,
kotlinc shows warnings only once a file has no errors, unless you add -Xreport-all-warnings. Teams usually make
warnings fail the build in CI, with -Werror on the command line or allWarningsAsErrors in Gradle, so that a
warning cannot reach production unnoticed.
Model answers open with any pass: ₹79 for 1 week ($2.99), one payment that never renews.
-
Mid to senior
A codebase of 300 Kotlin files builds with 140 warnings, and nobody reads them. The team wants allWarningsAsErrors in CI. How would you introduce it without stopping all work, and what would you do with each kind of warning?
-
Mid to senior
Your team's library calls allDistinct(), which needs @OptIn(ExperimentalStdlibApi::class) in Kotlin 2.4.20. Should the library opt in inside its own code, or pass the requirement on to its users, and what does each choice mean for them?
Key takeaways
- A diagnostic gives the file, the line and column, the severity and the message, then the source line with carets; read the message for what the compiler expected and what it found.
kotlinc2.4.20 hides warnings while a file has errors, so fix the first error, compile again, and expect warnings in the last round;-Xreport-all-warningsshows them all at once.- An opt-in requirement is fixed with
@OptInat the use or-opt-infor the module, and an experimental-feature error names its-Xflag, which brings no stability guarantee. - A warning can be a bug that compiles:
==on arrays printedfalsefor equal contents, andcontentEqualsgavetrue. -Werror, orallWarningsAsErrorsin Gradle, makes every warning fail the build and write no classes, which keeps such bugs out of what ships.
Terms in this lesson
- -Werror
- The kotlinc option that treats every warning as an error, so a build with any warning fails and writes no class files. In Gradle the same setting is allWarningsAsErrors.
- diagnostic
- A message from the compiler about the code: the file, line and column, a severity such as error or warning, and the text, followed by the source line with carets under the place.
- experimental language feature
- A language feature that compiles only with an -X compiler option that names it, such as -Xcollection-literals, and comes with no stability guarantee.
- K2 compiler
- The Kotlin compiler that became stable in Kotlin 2.0.0. It checks the code and reports the diagnostics of Kotlin 2.x.
- opt-in requirement
- A mark on an API, such as @ExperimentalStdlibApi, that makes code using it fail to compile, or warn, until the code accepts it with @OptIn or the -opt-in compiler option.
References
- Kotlin compiler options (-Werror, -Wextra, -opt-in, -X options) (JetBrains (kotlinlang.org))
- Opt-in requirements (JetBrains (kotlinlang.org))
- Compiler options in the Kotlin Gradle plugin (JetBrains (kotlinlang.org))
- K2 compiler migration guide (JetBrains (kotlinlang.org))
- Kotlin releases (2.0.0, the stable K2 compiler) (JetBrains (kotlinlang.org))
- Arrays: compare arrays (JetBrains (kotlinlang.org))
- What's new in Kotlin 2.4.0 (experimental collection literals) (JetBrains (kotlinlang.org))
Report a problem with this lesson
Kept only in this browser. Your Learn progress