skip to content

How does the Kotlin JVM plugin lay out source sets, and how do you add or customize Kotlin source directories?

level: seniorimportance: should knowfreq 40%

answer

  1. main/test source sets + Kotlin SourceDirectorySet
  2. Defaults: src/main/kotlin, src/test/kotlin, also src/main/java
  3. sourceSets["main"].kotlin.srcDir(...) to add a root
  4. create(...) -> auto compileXKotlin task + Ximplementation config
  5. JVM source sets != KMP commonMain/jvmMain dependsOn

basics

~10 s

The plugin creates main and test source sets, compiling code in src/main/kotlin and src/test/kotlin (plus the matching java folders). You can add or change directories through the sourceSets configuration in the build script.

solid answer

~40 s

For Kotlin/JVM the plugin reuses Gradle's **Java source-set model**: it creates `main` and `test` source sets and adds a **Kotlin `SourceDirectorySet`** to each, defaulting to `src/main/kotlin` and `src/test/kotlin`, while still compiling `src/main/java`. Each source set maps to a compile task (`compileKotlin` for `main`, `compileTestKotlin` for `test`) and to configurations (`implementation`, `testImplementation`, etc.). To customize, you configure `sourceSets["main"].kotlin.srcDir("src/extra/kotlin")` or define a brand-new source set with `sourceSets.create("integrationTest")`, which the plugin extends with its own Kotlin compile task and configurations. Note JVM source sets differ from **Kotlin Multiplatform** source sets (`commonMain`, `jvmMain`, hierarchical with `dependsOn`)—that's a sibling topic; here we stay on the single-target JVM model where source sets follow Gradle's Java conventions.

code

kotlin · 17 lines
kotlin
// Add a generated-sources directory and a new integrationTest source set
sourceSets {
    named("main") {
        kotlin.srcDir("src/generated/kotlin")
    }
    val integrationTest by creating {
        kotlin.srcDir("src/integrationTest/kotlin")
        compileClasspath += sourceSets["main"].output
        runtimeClasspath += sourceSets["main"].output
    }
}

tasks.register<Test>("integrationTest") {
    val it = sourceSets["integrationTest"]
    testClassesDirs = it.output.classesDirs
    classpath = it.runtimeClasspath
}

go deeper

for a junior

Knows code goes in src/main/kotlin and tests in src/test/kotlin.

for a middle

Explains the main/test source sets, their compile tasks, and that src/main/java is also compiled.

for a senior

Adds/configures source sets, wires classpaths, and knows each created source set yields its own compile task and configurations.

for a principal

Designs source-set topology for integration tests/generated code, and articulates how JVM source sets differ from the hierarchical KMP model.

## The model: Kotlin/JVM rides on Gradle Java source sets A **source set** is a named group of source files compiled together with its own classpath/configurations. The Java plugin defines `main` and `test`; the Kotlin/JVM plugin **augments** each with a **Kotlin `SourceDirectorySet`**. Defaults: - `main` → `src/main/kotlin` (and `src/main/java`, since Kotlin can compile mixed sources) - `test` → `src/test/kotlin` (and `src/test/java`) Each source set is tied to: - a **compile task** — `compileKotlin` (main), `compileTestKotlin` (test); - **dependency configurations** — `implementation`, `api`, `compileOnly`, `runtimeOnly`, and the `test*` variants; - an **output** that feeds `jar`/`test`/`build`. ## Adding a directory to an existing source set ```kotlin sourceSets { named("main") { kotlin.srcDir("src/generated/kotlin") } } ``` The `kotlin` accessor is the Kotlin `SourceDirectorySet`; `srcDir` appends a root so kotlinc picks it up. ## Defining a new source set (e.g. integration tests) ```kotlin val integrationTest by sourceSets.creating { kotlin.srcDir("src/integrationTest/kotlin") compileClasspath += sourceSets["main"].output runtimeClasspath += sourceSets["main"].output } dependencies { "integrationTestImplementation"(sourceSets["main"].output) } val integrationTestTask = tasks.register<Test>("integrationTest") { testClassesDirs = integrationTest.output.classesDirs classpath = integrationTest.runtimeClasspath } ``` Creating the source set makes Gradle/KGP generate a `compileIntegrationTestKotlin` task and `integrationTestImplementation`/`integrationTestRuntimeOnly` configurations automatically; you wire the classpath to `main` and register a `Test` task to run it. ## Not to be confused with Multiplatform In **Kotlin Multiplatform (KMP)** source sets are different: `commonMain`, `jvmMain`, `iosMain`, etc., arranged hierarchically via `dependsOn`, and configured under the `kotlin { sourceSets { } }` MPP DSL. That is a separate sibling topic. For single-target **Kotlin/JVM**, source sets follow the plain **Gradle Java convention** described above—the plugin just layers Kotlin compilation onto it. ## Why this matters Understanding the source-set model lets you: split slow integration tests out of `test`, add generated-code directories, or expose extra roots—without fighting the plugin, because every source set deterministically yields its own compile task and configurations.

  • When you create a source set named integrationTest, what tasks/configurations appear?
    A compileIntegrationTestKotlin task plus integrationTestImplementation, integrationTestRuntimeOnly, etc., are created automatically; you still register a Test task to actually run it.
  • Can Kotlin compile code under src/main/java?
    Yes. The Kotlin/JVM plugin compiles both kotlin and java roots in a source set so Kotlin and Java can be mixed in one module.

saying these in an interview costs you the question

  • Mixing up JVM source sets with KMP commonMain/jvmMain
  • Thinking only src/main/kotlin is compiled and src/main/java is ignored
  • Forgetting to wire compile/runtime classpath to main for a new source set
  • Believing a new source set's directories appear without create/srcDir configuration

context