Skip to content

2. Setting Up the Development Environment

The Kotlin developer’s work environment consists of three independent components:

  • the JDK (“Java Development Kit”): Kotlin, as we use it in this document, is compiled into bytecode and executed by the Java Virtual Machine (JVM), just like a Java program. JDK provides this virtual machine, the [java] command that runs programs, the standard Java libraries SE (which Kotlin uses directly: files, networking, dates, threads, etc.), and tools such as [javap] or [jar];
  • a project build tool, in this case Gradle: it downloads the Kotlin compiler and the libraries a project needs, compiles the project, runs its tests, and packages it;
  • an editor or an IDE (Integrated Development Environment): [IntelliJ IDEA], designed by the company that created Kotlin, is the natural choice; [VS Code] with the Kotlin extension is another option. IDE is based on JDK and can read Gradle projects.

The Kotlin compiler itself is not included in this list: Gradle downloads it for each project, and IntelliJ IDEA bundles it. However, you can install it separately using the command [kotlinc] to compile a single file: we’ll do this for our first program, but it’s optional.

This document uses the commands [java], [kotlinc], and [gradlew] for all operations: they work the same way on Windows, Linux, and macOS, regardless of the editor you choose.

2.1. JDK 25

2.1.1. Installation

There are several distributions of JDK, all built from the OpenJDK source code and equivalent for our purposes. We recommend Eclipse Temurin, a free and open-source distribution from the Eclipse Foundation, which can be downloaded at https://adoptium.net: There, select version 25—LTS—the operating system, and the package type (on Windows, a [.msi] installer). On Windows, you can also install it from a terminal (PowerShell or command prompt) using the [winget] package manager:

winget install EclipseAdoptium.Temurin.25.JDK

You must select JDK (which contains everything) and not just JRE alone (which only allows you to run pre-compiled programs). When installing on Windows, check the options that add JDK to [PATH] and set the [JAVA_HOME] environment variable: Gradle, the [kotlinc] compiler, and the IDE files use this to locate the JDK file. On Linux, JDK is available in the repositories of major distributions (for example, [sudo apt install openjdk-25-jdk] on Ubuntu).

After installation, open a new terminal to verify that the [java] command is available and displays the version of JDK:

1
2
3
4
> java --version
openjdk 25.0.4 2026-07-21 LTS
OpenJDK Runtime Environment Temurin-25.0.4+7 (build 25.0.4+7-LTS)
OpenJDK 64-Bit Server VM Temurin-25.0.4+7 (build 25.0.4+7-LTS, mixed mode, sharing)

These results were obtained using JDK Temurin 25.0.4, which was used to write this document. The exact version depends on the installation date: bugfix updates are released quarterly. All JDK and 25.0.x versions are suitable for this document. Multiple JDK instances can coexist on the same computer: the first one found in the [PATH] directory responds to the [java] command.

The JDK also contains the Java compiler [javac] ([javac --version] displays [javac 25.0.4]). We won’t need it: the Kotlin compiler generates the bytecode itself. But it’s there, and a Gradle project can easily mix Kotlin and Java files, which work together.

2.1.2. The commands for JDK

Here are the commands from JDK that we’ll use:

Command
Role
[java -jar program.jar]
Runs the program contained in a [.jar] archive whose manifest specifies the main class
[java -cp classes MyClass]
executes the function [main] of a compiled class, searched for in the class path ([-cp], “class path”)
[javap -cp classes MyClass]
displays the members of a compiled class: we’ll use this to see what the Kotlin compiler has produced
[jar]
creates or reads a [.jar] archive (a ZIP file of compiled classes)

JDK also provides the interactive interpreter [jshell], but it only accepts Java statements. To test a Kotlin expression without writing a program, use the IntelliJ and IDEA tools described below instead.

2.2. The Kotlin Command-Line Compiler

2.2.1. Installation

This installation is optional: Gradle projects do not require it, and IntelliJ IDEA includes its own compiler. It allows you to compile and run a standalone Kotlin file from a terminal without creating a project.

The compiler can be downloaded from the Kotlin project's released versions page, specifically GitHub (https://github.com/JetBrains/kotlin/releases): In the “Assets” section of version 2.4.20, download the archive [kotlin-compiler-2.4.20.zip]. Unzip it into a folder, for example [C:\Program Files\kotlinc], and add its subfolder [bin] to [PATH] (Windows: “Edit system environment variables”). You can also use a package manager: [scoop install kotlin] on Windows (with Scoop), or SDKMAN! on Linux and macOS ([sdk install kotlin 2.4.20]). The compiler requires a JDK, which it locates via the [JAVA_HOME] variable or the [PATH] variable.

Verify the installation in a new terminal:

> kotlinc -version
info: kotlinc-jvm 2.4.20 (JRE 25.0.4+7-LTS)

The compiler version (2.4.20) is followed by the version of the JVM that runs it (JDK 25).

2.2.2. Compiler Commands

The [bin] directory contains two main commands, [kotlinc] (the compiler) and [kotlinr] (the launcher, “runner”):

Command
Role
[kotlinc File.kt -d classes]
compiles one or more source files and places the resulting [.class] files in the [classes] folder
[kotlinc File.kt -include-runtime -d prog.jar]
compiles and produces an executable archive named [.jar], which also contains the Kotlin standard library
[kotlinr -cp classes MyClass]
as [java -cp classes MyClass], adding the Kotlin standard library to the classpath
[kotlinr script.main.kts]
runs a Kotlin script (file [.main.kts]), with no visible compilation step

A Kotlin script is a sequence of instructions executed from top to bottom, without a function [main]. For example, the following script [essai.main.kts]:

val s = "Kotlin " + 2.4
println(s.length)

runs as follows:

> kotlinr essai.main.kts
10

Prior to Kotlin 2.4.20, the launcher was called [kotlin]; it was renamed to [kotlinr] in Kotlin 2.4.20 to avoid a naming conflict with another command, [kotlin], which belongs to the “Kotlin Toolchain” tool. The old name still works in version 2.4.20 of the compiler (it is not officially deprecated), but the Kotlin documentation now recommends [kotlinr].

Without any arguments, the command [kotlinc] used to launch an interactive interpreter (a “REPL,” equivalent to [jshell]). This feature has now been deprecated:

> kotlinc
error: Kotlin REPL is deprecated and should be enabled explicitly for now; please use the '-Xrepl' option

To try out a few lines of Kotlin, you should instead use a scratch file created with IntelliJ IDEA (“File / New / Scratch File,” Kotlin language), which displays the results next to each line.

2.3. Gradle

2.3.1. The Role of Gradle

A real-world application uses libraries that are not part of JDK or the Kotlin standard library: a database driver, a testing framework, Hibernate, Spring, coroutines… They are distributed as [.jar] archives via a public repository, Maven Central (https://central.sonatype.com). A build tool reads the project description to determine which libraries it needs, downloads them (along with the libraries on which they depend), and then compiles, tests, and packages the project.

The two major build tools in the Java world are Maven (used by the Java course on which this document is based) and Gradle. Kotlin can be used with both, but Gradle is the de facto standard in the Kotlin ecosystem: it is used by Android and by Kotlin projects created by IntelliJ IDEA, and its build files are written... in Kotlin. This document uses Gradle 9.8.

2.3.2. The Gradle wrapper: no installation required

There is no need to install Gradle. Each project in this document comes with the Gradle wrapper—a small launcher consisting of three files located in the project root:

  • [gradlew]: the launch script for Linux and macOS ([./gradlew]);
  • [gradlew.bat]: the same script for Windows ([gradlew]);
  • the [gradle/wrapper] folder, which contains the launcher program ([gradle-wrapper.jar]) and its configuration ([gradle-wrapper.properties]).

The file [gradle-wrapper.properties] specifies the Gradle version to use:

distributionUrl=https\://services.gradle.org/distributions/gradle-9.8.0-bin.zip

The first time it is run, [gradlew] downloads this version of Gradle and stores it on the computer; on subsequent runs, it uses it directly. Each project thus uses the version of Gradle with which it was written, regardless of which version may be installed on the computer. Gradle only needs a JDK to run, which it finds either through the [JAVA_HOME] variable or the [PATH] variable.

In this document, commands are written in the Windows format ([gradlew build]); on Linux or macOS, they are written as [./gradlew build]. In PowerShell, you must also write [.\gradlew build].

If you still want to use the command [gradle] (to create a project with [gradle init], for example), download the [gradle-9.8.0-bin.zip] archive from https://gradle.org/releases, unzip it, and add its [bin] subfolder to [PATH]; or use [scoop install gradle] or [sdk install gradle]. Then verify the installation:

> gradle --version
------------------------------------------------------------
Gradle 9.8.0
------------------------------------------------------------

Build time:    2026-09-24 13:40:00 UTC
Revision:      a927be5e08efe79e0b87ada06c762dde6bb9f8b8

Kotlin:        2.4.10
Groovy:        4.0.33
Ant:           Apache Ant(TM) version 1.10.17 compiled on April 6 2026
Launcher JVM:  25.0.4 (Eclipse Adoptium 25.0.4+7-LTS)
Daemon JVM:    /home/claude/tools/jdk-25.0.4+7 (no Daemon JVM specified, using current Java home)
OS:            Linux 6.18.44-fc-v64 amd64

These results were obtained on Linux. The line [Kotlin:] provides the version of Kotlin integrated into Gradle, which is used to read the [.gradle.kts] build files: it is independent of the version of the Kotlin compiler used to compile our programs (2.4.20), which is set by each project. The line [Launcher JVM] indicates the JDK that runs Gradle.

2.3.3. Gradle Commands

The [gradlew] command is followed by one or more tasks; it is entered in the project’s root directory, the one containing the [settings.gradle.kts] file:

Command
Role
[gradlew build]
compiles the source code for [src/main/kotlin], runs the tests, and creates the project archive [.jar] in [build/libs]
[gradlew run]
compiles and then runs the project’s main class ([application] plugin)
[gradlew test]
compiles, then runs the unit tests for [src/test/kotlin] (Chapter 6)
[gradlew clean]
deletes the [build] folder
[gradlew tasks]
displays the list of available tasks in the project
[gradlew installDist]
creates an installable version of the program in [build/install]: its archives and launch scripts
[gradlew bootRun]
runs a Spring Boot application (Chapter 13)

The tasks are interdependent: running [run] also triggers the compilation, and Gradle only re-runs the parts that have changed since the previous build. The [-q] (“quiet”) option suppresses Gradle messages and displays only those from the executed program; the [-Pname=value] option sets a property that the build file can read: we’ll use it to choose which program to run. The arguments for the program launched by [run] are passed via the [--args="a b"] option.

The first command run in a project is slow: Gradle starts a background process (the Gradle “daemon”), which remains active for a few hours to speed up subsequent commands and downloads what the project needs.

2.3.4. Maven Central and the Gradle Cache

The libraries downloaded by Gradle are stored locally in the Gradle cache, in the [%USERPROFILE%\.gradle\caches] folder (Linux and macOS: [~/.gradle/caches]). They are downloaded only once and are then reused by all projects. Therefore, an Internet connection is required when building a project for the first time. The same [.gradle] folder contains, within [wrapper/dists], the Gradle versions downloaded by the wrapper.

A library is identified by its coordinates—three names separated by colons: the group (often a reverse domain name, such as [org.hibernate.orm]), the library name, and its version, for example, [tools.jackson.core:jackson-databind:3.1.5]. These coordinates are what you enter in a project’s build file, and they can be found by searching for the library on the Maven Central website.

2.3.5. The Kotlin syntax: DSL

The project’s build file is named [build.gradle.kts]. The extension [.kts] indicates that it is a Kotlin script: the file is written in a dedicated language (“DSL,” “Domain Specific Language”) built using Kotlin syntax. Gradle also supports [build.gradle] files written in Groovy, which are found in older projects; since Gradle 8.2, Kotlin DSL has been the default format. Here is a typical excerpt:

1
2
3
4
5
6
7
8
plugins {
    kotlin("jvm") version "2.4.20"
    application
}

dependencies {
    implementation("tools.jackson.core:jackson-databind:3.1.5")
}

Even without knowing Kotlin yet, you can read this text as a configuration:

  • a name followed by a block enclosed in curly braces ([plugins { ... }], [dependencies { ... }]) is a section of the configuration. In Kotlin, this is a function call that takes a code block as a parameter (a lambda, Chapter 7);
  • [kotlin("jvm")] and [implementation("...")] are function calls; the strings are enclosed in double quotes;
  • [version = "1.0"] assigns a value to a project property.

One advantage of this format is that IntelliJ IDEA recognizes it as Kotlin code: it offers code completion and flags errors as you type.

2.4. The editor

2.4.1. IntelliJ IDEA

IntelliJ IDEA, from the company JetBrains, is the recommended editor for this document. JetBrains is also the company that developed Kotlin (the first stable version of the language was released in 2016) and continues to develop it: Kotlin support is built into IntelliJ IDEA, with no extensions required, and it is the version that most closely tracks the language’s evolution. Its basic features, which are more than sufficient for this document, are free.

IntelliJ IDEA recognizes Gradle projects: select “File / Open,” then choose the folder containing the [settings.gradle.kts] file; IntelliJ launches Gradle to read the project (you can monitor the process in the “Build” window), downloads the libraries, and then displays a “Gradle” view that provides access to all the project’s tasks. You can then run a program by clicking the green triangle in the margin next to its function [main] ([Ctrl-Shift-F10]), or run a test by clicking the same triangle next to the test function. You specify the JDK to use in “File / Project Structure / SDK”; for a Gradle project, it is generally the project’s [jvmToolchain(25)] declaration (see below) that specifies the JDK for compilation.

IntelliJ IDEA also offers Kotlin-specific features: automatic conversion of a Java file to Kotlin (“Code / Convert Java File to Kotlin File,” or simply by pasting Java code into a Kotlin file), display of the bytecode generated by the compiler (“Tools / Kotlin / Show Kotlin Bytecode”), and the draft files mentioned earlier.

2.4.2. VS Code

VS Code is a free, cross-platform editor. To program in Kotlin, install the Kotlin extension published by JetBrains (go to the “Extensions” menu, search for “Kotlin,” and select the one whose editor is JetBrains). This official extension is new: it uses the same code-analysis engine as IntelliJ and IDEA and can import Gradle projects, but it does not yet offer all the features of IDE or JetBrains, and its functionality changes from one version to the next. Check the extension’s page to see what it supports at the time of installation. For the purposes of this document, it is suitable for reading and editing programs; you can always run them from the built-in terminal (“Terminal / New Terminal”) using the [gradlew] commands provided in the text.

In VS Code, open a chapter folder ("File / Open Folder"), for example, the [kotlin-oct-2026/03-basics] folder.

2.4.3. Android Studio

Kotlin is also the language recommended by Google for Android apps. Android Studio, built on IntelliJ IDEA, is dedicated to this type of app, which is not covered in this document.

In the remainder of this document, operations are described using the commands [java], [kotlinc], and [gradlew], which produce the same results regardless of the editor.

2.5. A Sample Program: The Hello.kt File

2.5.1. The program

A Kotlin program can be a simple file containing a function named [main], without a class declaration. Let’s create the following file, [Hello.kt] (in the [02-installation] examples folder):

1
2
3
4
// first Kotlin program: a top-level main function
fun main() {
    println("1st try with Kotlin 2.4")
}

Comments:

  • line 1: a comment. Everything following [//] until the end of the line is ignored by the compiler;
  • line 2: the function [main], the program’s entry point. The keyword [fun] introduces a function. It is declared at the file level (“top-level function”), outside of any class; it does not need to be declared as [public] (this is the default visibility in Kotlin) nor does it need to receive any program arguments;
  • Line 3: the program’s only statement: it prints a line of text to the screen. The function [println] (and [print], which does not wrap to the next line) is part of the Kotlin standard library; This is the equivalent of [System.out.println] in Java. Note the absence of a semicolon at the end of the statement: it is optional in Kotlin and is not written.

The program is a bit simpler than its equivalent in the Java course ([void main() { IO.println(...); }]), but the real difference lies elsewhere: the high-level function [main] has existed in Kotlin since its first release, whereas Java has only supported it since Java 25.

2.5.2. Compilation and Execution

We compile the program with [kotlinc], specifying an executable archive named [.jar], and then run it using the [java] command from JDK:

1
2
3
> kotlinc Hello.kt -include-runtime -d hello.jar
> java -jar hello.jar
1st try with Kotlin 2.4
  • [-d hello.jar]: the compilation destination. If it ends with [.jar], the compiler produces an archive; otherwise, it is a folder in which it stores the [.class] files;
  • [-include-runtime]: The archive also contains the Kotlin standard library ([kotlin-stdlib]), which Kotlin programs need at runtime, and a manifest that specifies the main class. The program can thus be run by [java -jar] on any machine equipped with JDK (or JRE), even if Kotlin is not installed on that machine. On the other hand, the archive is over 5 MB in size, consisting mainly of the standard library;
  • compilation takes a few seconds: the Kotlin compiler, itself a Java program, must first start up. Gradle and IntelliJ IDEA avoid this overhead by keeping a compiler active between compilations.

There is no equivalent in Kotlin to the Java command [java Hello.java], which compiles and executes a source file in a single step; the closest equivalent is the [.main.kts] script presented above.

2.5.3. What the Compiler Produces

The Java Virtual Machine (JVM) recognizes only classes: a function cannot exist outside of a class. The Kotlin compiler therefore wraps the high-level functions in a file into a class that it generates, naming it after the file: the file name followed by [Kt]. For [Hello.kt], the generated class is [HelloKt]. The command [javap] in JDK displays its members:

1
2
3
4
5
6
> javap -cp hello.jar HelloKt
Compiled from "Hello.kt"
public final class HelloKt {
  public static final void main();
  public static void main(java.lang.String[]);
}
  • The class [HelloKt] is [public final];
  • it contains our function [main], which has become a static method with no parameters;
  • and a second method, [main(String[])], added by the compiler: this is the method [public static void main(String[] args)] that the virtual machine knows how to invoke. It simply calls the first one. We will return to this mechanism in Chapter 3.

It is this class name that identifies the program to be executed: the manifest for [hello.jar] contains the line [Main-Class: HelloKt], and one can also write [java -cp hello.jar HelloKt].

Unlike in Java, there are no restrictions on the filename: it does not have to be a valid identifier or match the name of a class it contains. A file named [first-try.kt] is accepted, and its generated class is named [First_tryKt] (the compiler capitalizes the first letter and replaces characters prohibited in an identifier with [_]). By convention, however, files are given names that start with a capital letter and contain no hyphens, such as [Hello.kt] or [Basics01.kt].

On Windows, accented characters written as [println] display correctly in the terminal: the virtual machine converts the text to the terminal’s encoding. Source files must be saved in UTF-8, the default encoding for VS Code and IntelliJ IDEA, and the one that the Kotlin compiler always assumes. The situation is different when the program is launched by Gradle ([gradlew run], or the green triangle in a Gradle project): its output is no longer a console but a pipe to Gradle, and the virtual machine then encodes it in the historical Windows encoding (windows-1252), so that IntelliJ IDEA, which waits for UTF-8, displays a placeholder character (a diamond containing a question mark) in place of each accented character. The course projects resolve this issue in their [build.gradle.kts] file (see the next section). For the built-in terminal of IntelliJ IDEA (PowerShell), you can also switch the console to UTF-8 by typing [chcp 65001] at the start of the session, or once and for all by specifying [powershell.exe -NoExit -Command "[Console]::OutputEncoding = [Text.Encoding]::UTF8"] as the interpreter in “File / Settings / Tools / Terminal / Shell path.”

The command [kotlinc] is suitable for single-file programs that use only the standard library. As soon as a program requires an external library, you must download it—along with the libraries it depends on—and pass it to the compiler and then to the virtual machine: this involves creating a Gradle project. This is what we will do for all the examples in this document.

2.6. A Gradle Project

2.6.1. Project Structure

A Gradle project is a folder containing a build file named [build.gradle.kts] and source files organized according to a standard directory structure. The root of a Gradle build also contains a file named [settings.gradle.kts], which provides the build’s name and a list of the projects it groups together, as well as the wrapper. The [02-installation] folder in the examples is a build that groups two projects, [FirstProgram] and [TwoHands]:

02-installation/
├── gradlew
├── gradlew.bat
├── gradle/
│   └── wrapper/
│       ├── gradle-wrapper.jar
│       └── gradle-wrapper.properties
├── settings.gradle.kts
├── build.gradle.kts
├── Hello.kt
├── FirstProgram/
│   ├── build.gradle.kts
│   └── src/
│       └── main/
│           └── kotlin/
│               └── first/
│                   └── Main.kt
└── TwoHands/
    ├── build.gradle.kts
    └── src/
        └── main/
            └── kotlin/
                └── twohands/
                    ├── P1.kt
                    └── P2.kt
  • [src/main/kotlin] contains the program’s source files; [src/main/resources] would contain the data or configuration files to be placed alongside the compiled classes; [src/test/kotlin] would contain the tests (Chapter 6);
  • Under [src/main/kotlin], the folder structure mirrors that of the packages (Chapter 4): the file [Main.kt] from the package [first] is located in the folder [first]. Kotlin does not enforce this correspondence, unlike Java, but recommends it, and we will always follow it;
  • The file [Hello.kt] does not belong to any project: it is placed there to be compiled by [kotlinc], and Gradle ignores it.

A standalone project (not grouped with others) has the same structure, with its own [settings.gradle.kts] and wrapper files: this is the case for the project in Chapter 3. You can create this directory structure manually, or let IDE create it (IntelliJ: “File / New / Project,” select “Kotlin” as the language and “Gradle” as the build system, with the Kotlin DSL).

We’ll look at the root files in the next section. The [build.gradle.kts] file in the [FirstProgram] project is as follows:

// the [FirstProgram] project: a Kotlin program executed by the JVM
plugins {
    // the Kotlin plugin for the JVM (its version is declared in the root project)
    kotlin("jvm")
    // the [application] plugin: run and installDist tasks
    application
}

// the project coordinates
group = "examples.installation"
version = "1.0"

// the repository where libraries are looked up
repositories {
    mavenCentral()
}

dependencies {
    // no library apart from the Kotlin standard library, added by the plugin
}

kotlin {
    // the code is compiled and run with JDK 25
    jvmToolchain(25)
    compilerOptions {
        // a compiler warning is treated as an error
        allWarningsAsErrors = true
        // function parameter names are kept in the bytecode
        javaParameters = true
    }
}

application {
    // the class containing the main function run by [gradlew run]
    mainClass = "first.MainKt"
}

// on Windows, the screen output of the program launched by Gradle (run, bootRun...) is encoded in UTF-8,
// otherwise accented characters are garbled in the IntelliJ IDEA console or the terminal
tasks.withType<JavaExec>().configureEach {
    jvmArgs("-Dstdout.encoding=UTF-8", "-Dstderr.encoding=UTF-8")
}

Comments:

  • line 1: a comment. The build file is written in Kotlin: comments are written just as they would be in a program;
  • lines 2–7: the plugins applied to the project. These provide Gradle with the tasks we need:
    • Line 4: The Kotlin plugin for JVM. It adds tasks that compile the files in [src/main/kotlin] and automatically adds the Kotlin standard library to the project's dependencies. The notation [kotlin("jvm")] is a shorthand for the plugin’s full identifier, [org.jetbrains.kotlin.jvm]. Its version is not specified here because the project is part of a multi-project build where the root project sets this version for all of them (see the next section). In a standalone project, you would write [kotlin("jvm") version "2.4.20"]: this version determines the version of the Kotlin compiler used;
    • line 6: the [application] plugin, which adds the tasks [run] (run the program) and [installDist] (prepare an installable version);
  • lines 10–11: the project, group, and version identifiers, which identify the project by its name ([FirstProgram], the name of its folder). These are also the identifiers that another project could use to reference this one, similar to how a library from Maven Central is referenced (Chapter 6);
  • lines 14–15: the repository where Gradle looks for the project’s libraries, in this case Maven Central;
  • lines 18–20: the libraries used by the project. This project does not use any, apart from the standard Kotlin library that the Kotlin plugin adds automatically;
  • line 24: the project’s toolchain: the code is compiled for Java 25 and run by [gradlew run] with a JDK 25, even though Gradle itself is run by a different JDK. Gradle searches for this JDK among those installed on the machine. If it does not find any, it fails with the error message [Cannot find a Java installation on your machine ... matching: {languageVersion=25 ...}], unless the build declares the [foojay-resolver-convention] plugin—which all projects in the course do (see the next section): Gradle then downloads a JDK 25 on its own. In IntelliJ IDEA, you can also install this JDK via “File / Project Structure / SDK / Add SDK / Download JDK";
  • line 27: A compiler warning is treated as an error and stops the compilation. The Kotlin compiler flags many issues (unused variables, unnecessary type casting, superfluous [!!] operators, etc.): we want programs that produce none of these;
  • line 29: The compiler preserves the names of function parameters in the bytecode. Some libraries use this, for example, Jackson to create an object from a JSON document (Chapter 5) or Spring (Chapter 13);
  • lines 33–35: the class whose function [main] is executed by [gradlew run]. The name is that of the class generated by the compiler for the [Main.kt] file in the [first] package: [first.MainKt];
  • lines 38–41: All tasks that execute a Java program ([JavaExec]), such as [run], receive two options from the virtual machine: the program’s screen output ([System.out], [System.err]) is encoded in UTF-8. Without them, accented characters written by the program would be corrupted in the console of IntelliJ and IDEA under Windows (see the note on accents above). [tasks.withType<JavaExec>()] refers to all tasks of this type, and [configureEach { … }] configures each of them. All course projects that run a program contain this configuration.

This project does not use any libraries. A project that uses a library declares it in the [dependencies] section using its coordinates, as we will do starting in Chapter 5:

1
2
3
dependencies {
    implementation("tools.jackson.core:jackson-databind:3.1.5")
}

The term [implementation] indicates that the library is required to compile and run the program. You’ll also encounter [runtimeOnly] (required only for execution, such as a database driver) and [testImplementation] (required only for testing). Gradle will then download [jackson-databind] and the libraries it depends on.

The file [Main.kt] contains the program:

1
2
3
4
5
6
package first

// the main program of the [FirstProgram] project
fun main() {
    println("1st try with a Gradle / Kotlin 2.4 project")
}
  • line 1: the file belongs to the [first] package. The full name of the class generated for this file is therefore [first.MainKt];
  • Lines 4–6: the function [main], in the same form as in [Hello.kt]. A Kotlin file can contain any number of functions and classes, whether public or not: the file name does not depend on its contents.

2.6.2. Compilation and Execution

The command [gradlew build], entered in the [02-installation] folder, compiles the build projects (and would run their tests if they had any): Gradle downloads the Kotlin plugin, the compiler, and the standard library as needed, then compiles the files in [src/main/kotlin] and places the resulting classes in the [build/classes/kotlin/main] folder for each project, preserving the package structure ([FirstProgram/build/classes/kotlin/main/first/MainKt.class]).

The [gradlew run] command compiles a project and then runs it. In a build involving multiple projects, the project is specified by its path, preceded by a colon: [:FirstProgram:run] is the task [run] of the project [FirstProgram]. With the option [-q], only the program output is displayed:

> gradlew -q :FirstProgram:run
1st try with a Gradle / Kotlin 2.4 project

You can also navigate to the project folder and type [..\gradlew -q run]: Gradle traces back to the [settings.gradle.kts] file in the root directory and executes the [run] task from the project in the current folder. On the other hand, typing [gradlew run] at the root directory would execute the [run] task for all projects that have one.

Once the project has been compiled, you can also run the class directly using the command [java], specifying the location of the classes (in the [FirstProgram] folder):

> java -cp build/classes/kotlin/main first.MainKt
1st try with a Gradle / Kotlin 2.4 project

This works here because this program does not actually use anything from the Kotlin standard library at runtime: the compiler replaces the call to [println] with a direct call to [System.out.println]. However, a program that uses, for example, a list ([listOf(1, 2, 3)]) fails in this way with the error [java.lang.NoClassDefFoundError: kotlin/collections/CollectionsKt]: the class [CollectionsKt] from the standard library cannot be found. You must then add the [kotlin-stdlib-2.4.20.jar] archive to the classpath (it is in the Gradle cache; the [lib] folder of the [kotlinc] compiler also contains it, under the name [kotlin-stdlib.jar]): [java -cp "build/classes/kotlin/main;path/kotlin-stdlib-2.4.20.jar" first.MainKt] on Windows (the separator is [:] on Linux and macOS). The command-line compiler’s [kotlinr -cp build/classes/kotlin/main first.MainKt] command does this for us.

The easiest way is to have Gradle build the program and everything it needs using the [installDist] task from the [application] plugin:

> gradlew -q :FirstProgram:installDist

It creates the [FirstProgram/build/install/FirstProgram] folder, which contains a [lib] folder (the project archive, the Kotlin standard library, and, if applicable, the project’s other libraries) and a [bin] folder with two launch scripts, [FirstProgram] (Linux, macOS) and [FirstProgram.bat] (Windows), which call [java] with the correct classpath. In this form, a program is delivered so that it can be copied to another computer running JDK:

> FirstProgram\build\install\FirstProgram\bin\FirstProgram
1st try with a Gradle / Kotlin 2.4 project

The [build] folder can be safely deleted ([gradlew clean]): it is recreated by the next compilation. The command [gradlew build] also produces the project’s archive [build/libs/FirstProgram-1.0.jar], which contains only its own classes.

2.7. A project with multiple modules

2.7.1. The multi-project build

A Gradle build can group together multiple projects, called its subprojects (or modules). The [settings.gradle.kts] file in the root directory lists them. The one in the [02-installation] folder is as follows:

// chapter 2: a Gradle build grouping the two projects of the folder

// Gradle downloads the JDK requested by jvmToolchain(25) itself if it does not find it on the machine
plugins {
    id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
}

rootProject.name = "installation"

// the subprojects, designated by their folder name
include("FirstProgram", "TwoHands")
  • Lines 3–6: a plugin for the build itself (not for a project), [foojay-resolver-convention]: When a project requests a JDK via [jvmToolchain(25)] that Gradle cannot find on the machine, Gradle downloads it from the foojay.io service and stores it in its cache. The first run takes a little longer, but the student no longer needs to have the correct JDK installed. The [plugins] block must be the first in the file. All builds in the course declare this plugin;
  • line 8: the build name (the root project). Without this line, it would be the folder name;
  • line 11: the subprojects, identified by their folder names. A subproject can be nested deeper: [include("TaxV5:tax-dao")] refers to the project in the [TaxV5/tax-dao] folder (Chapter 6).

The root project also has a file named [build.gradle.kts]:

1
2
3
4
5
// chapter 2: root project. The Kotlin plugin is declared here, with its version, without being applied:
// the subprojects apply it
plugins {
    kotlin("jvm") version "2.4.20" apply false
}
  • Lines 3–4: The Kotlin plugin is declared, along with its version, but it is not applied to the root project ([apply false]), which does not contain any code to compile. This declaration makes the plugin available to subprojects, which apply it via [kotlin("jvm")] without repeating the version: all subprojects thus use the same version of Kotlin, which can be changed in a single location. If each subproject declared the plugin version on its own, Gradle would load it multiple times, which the Kotlin plugin advises against.

In the [02-installation] folder, the [gradlew build] command compiles both projects; a IDE that opens this folder sees both projects at once. In Chapter 6, we’ll see that a subproject can depend on another ([implementation(project(":TaxV5:tax-dao"))]), and in Chapters 10 through 13, we’ll see that a build can even reuse projects from another build ([includeBuild]).

2.7.2. A project with multiple main functions

A project can contain multiple files, each with a [main] function: each is a possible entry point, and the class to be executed is chosen at runtime. The [TwoHands] project contains two files from the [twohands] package. The [P1.kt] file:

1
2
3
4
5
6
package twohands

// a first entry point of the [TwoHands] project
fun main() {
    println("main of the P1.kt file")
}

and the file [P2.kt], which is identical except for the name. Both functions are named [main] and belong to the same package: there is no conflict because they are located in two different classes, [twohands.P1Kt] and [twohands.P2Kt]. The [java] command executes the class specified to it (in the [TwoHands] folder, once the project has been compiled):

1
2
3
4
> java -cp build/classes/kotlin/main twohands.P1Kt
main of the P1.kt file
> java -cp build/classes/kotlin/main twohands.P2Kt
main of the P2.kt file

For the [gradlew run] command, the project’s [build.gradle.kts] file specifies the class to be executed:

1
2
3
4
5
6
application {
    // the program run by [gradlew run] is designated by the [main] property;
    // by default, it is the main function of the P2.kt file:
    // gradlew -q :TwoHands:run -Pmain=twohands.P1Kt
    mainClass = providers.gradleProperty("main").orElse("twohands.P2Kt")
}
  • Line 5: The class being executed is specified by the Gradle property [main], which is provided on the command line via the option [-Pmain=...]. The [providers.gradleProperty("main")] function reads this property; [orElse(...)] specifies the value to use if it has not been provided: in this case, the [twohands.P2Kt] class.

The rest of the file is the same as that of [FirstProgram], with the addition of a configuration for the [run] task ([standardInput = System.in]), which transmits keyboard input to the program: without it, a program launched by [gradlew run] cannot read the keyboard.

Results:

1
2
3
4
> gradlew -q :TwoHands:run
main of the P2.kt file
> gradlew -q :TwoHands:run -Pmain=twohands.P1Kt
main of the P1.kt file

This allows you to select the program to run without modifying the [build.gradle.kts] file. With IntelliJ and IDEA, the issue doesn’t arise: each [main] function has its own green triangle in the margin.

2.7.3. One package per program

The projects in the following chapters all use this mechanism, since each one contains many small programs. To prevent these programs from interfering with one another, this document follows a simple convention: each program has its own package, which bears its name in lowercase and is stored in a folder with the same name. Thus, the program [Basics04.kt] from Chapter 3 is the file [03-basics/src/main/kotlin/basics04/Basics04.kt], which begins with the line [package basics04]; its class is [basics04.Basics04Kt], and it is executed as follows:

gradlew -q run -Pmain=basics04.Basics04Kt

There are two reasons for this convention:

  • All programs in a project are compiled together. If they were in the same package, two programs could not declare a function or class with the same name (a function [display], a class [Person]…): the compiler would report a duplicate declaration. With one package per program, each has its own namespace;
  • a program consisting of multiple files is simply a package containing multiple files: in Chapter 4, the package [persons] contains the class [Person] in [Person.kt] and the main program in [Main.kt], which is executed by [-Pmain=persons.MainKt].

The text of each chapter provides, before the results of a program, the exact command that executes it. We will revisit this structure in Chapter 3.

2.8. Examples in this document

To run the examples in this document:

  • download the [kotlin-oct-2026] folder (link at the beginning of the document). It contains one folder per chapter ([02-installation], [03-basics], [04-classes]…), and each of these folders is a Gradle build, complete with its wrapper: a single project, or multiple subprojects grouped together by its [settings.gradle.kts] file;
  • Open a chapter’s folder in IntelliJ IDEA (“File / Open”); then run a program by clicking the green triangle next to its function [main];
  • or, in a terminal located in the chapter’s folder, run a program using [gradlew -q run -Pmain=package.ClassKt] (with the subproject path preceding [run] if there are multiple subprojects); the text for each chapter provides the exact command.

The first [gradlew] command in a chapter requires Internet access: Gradle then downloads its own distribution, the Kotlin plugin, and the project’s libraries, which it subsequently stores in its cache.

Chapters 10 and 11 use a database named MySQL; the installation of MySQL with [Laragon] is described in the appendix.