What Is Gradle and Why Kotlin Uses It
Build automation, dependency management, tasks, and the wrapper that make Kotlin projects reproducible.
Gradle is a build automation tool: it takes your source code and turns it into something runnable or distributable (a JAR, an Android APK, a Spring Boot fat-jar) by orchestrating every step in between โ compiling Kotlin, processing resources, running tests, and packaging the result. The Kotlin ecosystem leans on Gradle because the official Kotlin Gradle Plugin is the canonical way to compile Kotlin for the JVM, Android, Native, and multiplatform targets. Instead of invoking the compiler by hand with a long list of flags and classpaths, you declare *what* you want in a build script and Gradle figures out *how* to produce it, caching and skipping work that hasn't changed.
The single most valuable thing Gradle gives you is dependency management. You declare the libraries your project needs (for example a coroutines or serialization library) along with a version, and Gradle downloads them from repositories like Maven Central, resolves their transitive dependencies, and assembles the correct classpath for each configuration โ `implementation` for normal compile-and-runtime needs, `testImplementation` for test-only libraries, and so on. This means you never commit JAR files into your repo; you commit a short declaration and Gradle reproduces the exact same set of artifacts on any machine.
Everything Gradle does is modeled as a graph of *tasks*. A task is a unit of work with inputs and outputs โ `compileKotlin`, `test`, `jar`, `build`. Tasks declare dependencies on each other, so when you run `./gradlew build`, Gradle computes the order, runs only the tasks whose inputs changed (incremental builds), and can reuse results from a build cache. You rarely write tasks by hand at first; plugins like `kotlin("jvm")` or `application` register a rich set of tasks for you, and you simply configure them.
Kotlin projects use the Kotlin DSL for build scripts, so the build files end in `.kts` and are written in Kotlin itself โ giving you type safety and IDE autocompletion instead of Groovy's dynamic syntax. There are two distinct files. `settings.gradle.kts` is the project-level entry point: it names the root project and declares which subprojects (modules) belong to the build via `include(...)`, and it's also where you configure where plugins and dependencies are downloaded from. `build.gradle.kts` is per-module: it applies plugins, sets the Kotlin/JVM toolchain, and declares that module's dependencies and task configuration. A single-module project still has one of each; a multi-module project has one `settings.gradle.kts` and a `build.gradle.kts` in every module.
The Gradle Wrapper (`gradlew` on macOS/Linux, `gradlew.bat` on Windows) is what makes a build truly reproducible across machines. The wrapper is a small checked-in script plus `gradle/wrapper/gradle-wrapper.properties`, which pins the exact Gradle version. When a teammate runs `./gradlew build`, the wrapper downloads that precise Gradle version (if missing) and runs it โ so nobody needs Gradle installed globally and everyone builds with the identical engine. The golden rule: always invoke `./gradlew`, never a system-wide `gradle`, and commit the wrapper files to version control.
Modern Gradle (8.x and later) also encourages a *version catalog*: a `gradle/libs.versions.toml` file that centralizes dependency coordinates and versions in one place. Instead of scattering version strings across many `build.gradle.kts` files, you define them once and reference them as type-safe accessors โ `libs.kotlinx.coroutines` for a library, or `alias(libs.plugins.kotlin.jvm)` for a plugin. This keeps versions consistent across modules and makes upgrades a one-line change. Together โ the Kotlin DSL, the wrapper, and the version catalog โ give you builds that are typed, reproducible, and easy to maintain. Remember: Gradle builds aren't run inside this in-browser lesson, so treat the snippets here as accurate reference templates to copy into a real project.
// settings.gradle.kts โ names the build and declares its modulesrootProject.name = "my-kotlin-app"// The type-safe version catalog at gradle/libs.versions.toml is auto-detectedinclude(":app", ":core") // each becomes a module with its own build.gradle.kts
settings.gradle.kts: the entry point that defines the root project and includes subprojects.
// build.gradle.kts (module) โ applies plugins and declares dependenciesplugins {alias(libs.plugins.kotlin.jvm) // Kotlin Gradle Plugin, version from the catalogapplication // adds the `run` task}kotlin {jvmToolchain(21) // compile/run against JDK 21}repositories { mavenCentral() } // where dependencies are fetched fromdependencies {implementation(libs.kotlinx.coroutines) // catalog accessor, not a raw stringtestImplementation(kotlin("test")) // test-only dependency}application { mainClass = "com.example.AppKt" }
build.gradle.kts: per-module plugins, toolchain, repositories, and dependencies, all driven by the catalog.
# gradle/libs.versions.toml โ centralized versions and dependency coordinates[versions]kotlin = "2.1.0"coroutines = "1.9.0"[libraries]kotlinx-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }[plugins]kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
libs.versions.toml: one source of truth so versions stay consistent across modules.
๐ง Check your understanding
0/1 ยท 0/1 answered1. Why should you run `./gradlew build` instead of a globally installed `gradle build`?