Kotlin
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Null Safety
-
Section 4: Object-Oriented Kotlin
-
Section 5: Functional Kotlin
-
Section 6: Coroutines
-
Section 7: Collections Deep Dive
-
Section 8: Type System Deep Dive
-
Section 9: Interop and Tooling
-
Section 10: Kotlin DSLs and Patterns
-
Section 11: Testing and Quality
-
Section 12: Server-Side Kotlin
-
Section 13: Practical Projects
-
Section 14: Interview Practice
-
Section 15: More Practice Exercises
-
Section 16: More Standard Library
-
Section 17: Multiplatform Kotlin
-
Section 18: kotlin.collections In Depth
-
Section 19: kotlin.text In Depth
-
Section 20: kotlin.ranges and kotlin.sequences
-
Section 21: kotlin.io and File Handling
-
Section 22: kotlinx.coroutines Deep Dive
-
Section 23: kotlin.reflect
-
Section 24: Android Development with Kotlin Overview
-
Section 25: Kotlin Multiplatform Deep Dive
-
Section 26: Kotlin for Backend Deep Dive
-
Section 27: Kotlin Design Patterns
-
Section 28: Advanced Language Features
-
Section 29: More Practice Exercises
-
Section 30: More Interview Practice
-
Section 31: Kotlin Type System Deep Dive
-
Section 32: Kotlin Null Safety Advanced
-
Section 33: Kotlin Testing Deep Dive
-
Section 34: Kotlin Build Tooling Deep Dive
-
Section 35: Kotlin Serialization
-
Section 36: Kotlin Performance Considerations
-
Section 37: Kotlin Native Overview
-
Section 38: Kotlin for Data and Scripting
-
Section 39: More Coroutines Practice
-
Section 40: More Android-Adjacent Patterns
-
Section 41: More Practical Projects
-
Section 42: More Design and Architecture Practice
-
Section 43: Kotlin Language Evolution
-
Section 44: More Interview and Review
-
Section 45: Kotlin Delegation Patterns Deep Dive
-
Section 46: Kotlin Annotations Deep Dive
-
Section 47: Kotlin for Gradle Plugin Development
-
Section 48: Kotlin Concurrency Beyond Coroutines
-
Section 49: Kotlin Compiler Internals
-
Section 50: Real-World Kotlin Case Studies
-
Section 51: Final Practice Projects
-
Section 52: Kotlin for Server-Side Reactive Programming
-
Section 53: More Practice and Drills
-
Section 54: Kotlin Security Practices
159: Gradle Kotlin DSL Convention Plugins
By the time you're managing a project with ten or twenty modules, you'll notice a frustrating pattern: your build.gradle.kts files start looking identical. You're copy-pasting the same JVM target, the same compiler options, and the same set of testing libraries into every single module. It's a maintenance nightmare. If you want to bump the Java version from 17 to 21, you're doing a find-and-replace across twenty files. This is where Convention Plugins come in.
Why can't I just use a shared script file?
You might have seen people using apply(from = "common.gradle.kts"). Honestly? Avoid it. That's the old way of doing things. The problem with shared scripts is that they lack proper type safety and IDE support. When you use a Convention Plugin, you're writing actual Kotlin code that is compiled. This means you get autocomplete, refactoring tools, and compile-time checks for your build logic. I've spent way too many hours debugging a typo in a shared script only to realize it didn't fail until the actual execution phase. Convention plugins kill that problem.
Where does the code for these plugins actually live?
The most straightforward place to put this logic is in a special directory called buildSrc. Gradle treats buildSrc as a first-class citizen; it compiles everything inside it first and then puts those classes on the classpath of your main build scripts.
Your structure should look like this:
project-root/
├── buildSrc/
│ ├── build.gradle.kts
│ └── src/main/kotlin/
│ └── my-kotlin-library.gradle.kts
├── core/
│ └── build.gradle.kts
└── api/
└── build.gradle.kts
In buildSrc/build.gradle.kts, you just need to make sure the kotlin-dsl plugin is applied so you can write your plugins using Kotlin:
plugins {
`kotlin-dsl`
}
repositories {
mavenCentral()
}
How do I actually write a convention and apply it?
Let's say you want every "library" module in your project to have the same JVM target and the same testing setup. Instead of a complex class, you can use a "precompiled script plugin." This is just a .gradle.kts file inside buildSrc/src/main/kotlin.
Create a file named my-kotlin-library.gradle.kts:
plugins {
kotlin("jvm")
}
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
kotlinOptions {
jvmTarget = "17"
freeCompilerArgs = freeCompilerArgs + "-Xjsr305=strict"
}
}
dependencies {
// Every library module gets JUnit 5 by default
testImplementation(kotlin("test"))
testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")
}
tasks.test {
useJUnitPlatform()
}
Now, the magic part: the filename my-kotlin-library.gradle.kts automatically becomes the plugin ID. In your core/build.gradle.kts or api/build.gradle.kts, you can now replace all that boilerplate with a single line:
plugins {
id("my-kotlin-library")
}
dependencies {
// Only module-specific dependencies go here
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
}
What if I need to pass parameters to my convention?
Convention plugins are meant to be "opinions"—they define how things *should* be. If you find yourself wanting to pass a lot of parameters, you might be building a general-purpose plugin rather than a convention. However, a common trick I use is to define an extension. By creating a small data class in buildSrc, you can allow modules to "opt-out" of certain behaviors or provide a specific version string, while still keeping the core logic centralized in the plugin.
📋 Practical Task
Exercise: Create a Shared Testing Convention Plugin
You are working on a project with multiple modules, and you're tired of configuring MockK and JUnit 5 in every single build.gradle.kts file. Your goal is to centralize this into a convention plugin.
- Create a
buildSrcdirectory with the necessarybuild.gradle.ktsapplying thekotlin-dslplugin. - Inside
buildSrc/src/main/kotlin/, create a precompiled script plugin namedinternal-test-convention.gradle.kts. - In this plugin, configure the following:
- Apply the
kotlin("jvm")plugin. - Add
io.mockk:mockk:1.13.5as atestImplementationdependency. - Add
org.junit.jupiter:junit-jupiter:5.10.0as atestImplementationdependency. - Configure the
testtask to useuseJUnitPlatform().
- Apply the
- Apply this plugin to a dummy module (e.g.,
:core) using theid("internal-test-convention")syntax and verify that the dependencies are available without being explicitly declared in the module's own build file.
There are no comments for now.