
Converting a build.gradle file (Groovy) to build.gradle.kts (Kotlin DSL) is mostly mechanical. You wrap function arguments in parentheses, swap single quotes for double quotes, and add an equals sign to every property assignment. That is 90% of the work. The rest is knowing the few APIs that have a different Kotlin name, like minifyEnabled becoming isMinifyEnabled.
I first wrote this guide in 2021. Since then Kotlin DSL has become the default build language for new Android projects, so this is no longer an exotic migration, it is the path Android Studio already puts you on. I have kept the original side-by-side examples and added a section at the end on what changed by 2026, so the code you copy is still correct today.
Note: I use an Android project in this guide, but the same rules apply to any Kotlin or JVM project built with Gradle.
How to convert build.gradle to build.gradle.kts
The full migration comes down to a handful of repeatable steps. Do them in order and rebuild once at the end.
- Rename each
build.gradlefile tobuild.gradle.kts. Start with the project-level file, then the module (app) file. - In the project-level file, add
gradlePluginPortal()tobuildscript.repositories, wrap every classpath dependency in parentheses, and switch to double quotes. - Rewrite the
cleantask usingtasks.register, since the Groovytask clean(type: Delete)syntax does not exist in Kotlin. - In the module file, convert
plugins,dependencies, and theandroidblock: add parentheses to functions, double quotes to strings, and an equals sign to every property. - Replace API names that differ in Kotlin, such as
minifyEnabledwithisMinifyEnabledanddebug { }withgetByName("debug") { }. - Clean and rebuild the project so the IDE re-syncs against the new script.
The rest of this post is those steps in detail, with Groovy and Kotlin side by side.
How do you convert the project-level build.gradle?
The following snippet shows the same code in Groovy and Kotlin. The changes are small but easy to miss:
- Add
gradlePluginPortal()tobuildscript > repositories. - Add parentheses to the classpath dependencies and use double quotes (
") instead of single quotes ('). - Refactor the Gradle
cleantask: use thetasksvariable with theregisterextension function, and pass the task name, the type class, and a lambda action. Add parentheses afterdelete.
// From Groovy:
buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:7.0.3"
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31'
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
// To Kotlin:
buildscript {
repositories {
gradlePluginPortal() <- add this plugin
google()
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:7.0.3")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31") <- parenthesis and from single to double quotes
}
}
tasks.register("clean", Delete::class) {
delete(rootProject.buildDir)
} <- Refactor the code
That converts the project-level build.gradle into .kts.
How do you convert the app-level build.gradle?
Now for the module file. This is where most of the code lives, so I will take it block by block.
Plugins
The plugins block sits at the top of the file:
- For any
id, keepidbut add parentheses and double quotes. - For Kotlin plugins, replace
idwith thekotlin("name")helper.
// From Groovy
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
}
// To Kotlin
plugins {
id("com.android.application") <- Parenthesis and double quotes
kotlin("android") <- Replace id for Kotlin and double quotes
kotlin("kapt")
}
Dependencies
Dependencies are simple to update: add parentheses after the dependency handler and use double quotes for the coordinate string. Here are the common handlers side by side.
// Groovy
dependencies {
implementation project(path: ':your-module')
implementation 'androidx.core:core-ktx:1.7.0'
debugImplementation 'androidx.compose.ui:ui-tooling:1.0.5'
kapt 'androidx.room:room-compiler:2.3.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
}
// Kotlin
dependencies {
implementation(project(":your-module"))
implementation("androidx.core:core-ktx:1.7.0")
debugImplementation("androidx.compose.ui:ui-tooling:1.0.5")
kapt("androidx.room:room-compiler:2.3.0")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.3")
}
Note how project(path: ':your-module') becomes project(":your-module"). Groovy’s named-argument map syntax collapses into a plain string argument in Kotlin.
Kapt options
If the project uses the kapt Kotlin plugin, you can still toggle flags, but the equals sign is mandatory in Kotlin. Groovy lets you write the assignment with or without it; Kotlin does not.
// From Groovy:
kapt {
correctErrorTypes true
}
// To Kotlin:
kapt {
correctErrorTypes = true
}
The android block
This block is the longest, but the changes follow the same three rules. Pay attention to the lines marked in the comments:
- All
buildTypesare declared withgetByName("debug")instead of a baredebug { }. minifyEnabledbecomesisMinifyEnabled.- Every property gets an equals sign, every string gets double quotes, and functions like
proguardFilesget parentheses.
// From Groovy:
android {
compileSdk 31
defaultConfig {
applicationId "com.domain.appname"
minSdk 23
targetSdk 31
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary true
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
useIR = true
}
buildFeatures {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion compose_version
kotlinCompilerVersion '1.5.21'
}
packagingOptions {
resources {
excludes += '/META-INF/{AL2.0,LGPL2.1}'
}
}
}
// To Kotlin:
android {
compileSdk = 31
defaultConfig {
applicationId = "com.domain.appname"
minSdk = 23
targetSdk = 31
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
}
buildTypes {
getByName("debug") { <- change from debug {}
isMinifyEnabled = false <- change from minifyEnabled
}
getByName("release") {
isMinifyEnabled = true
proguardFiles( <- add parenthesis
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8.toString()
useIR = true
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = Versions.Compose
}
packagingOptions {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}
Why does Android Studio show errors after the conversion?
This process is usually straightforward, but after you rename the files and update the syntax there is a good chance your IDE, in my case Android Studio, will show errors even when the script is correct. That is the sync cache, not your code. You can clear it with a clean and rebuild, or by invalidating caches:
Build > Clean Project and Build > Rebuild Project
or
File > Invalidate Caches / Restart
If the errors survive that, they are usually real syntax issues, most often a missing equals sign on a property or a function call left without parentheses.
What changed by 2026?
The conversion rules above still hold, but a few pieces of the surrounding advice have moved on. If you are setting up a build today rather than migrating an old one, keep these in mind. I have left the original code untouched so nothing breaks, and I am flagging the modern equivalents separately.
- Kotlin DSL is the default. New Android Studio projects are generated with
.gradle.ktsfiles out of the box, so for greenfield work there is nothing to convert. - Version catalogs. New projects also ship a
gradle/libs.versions.tomlcatalog, and dependencies are referenced asimplementation(libs.androidx.core.ktx)instead of a hardcoded coordinate string. It centralises versions across modules. rootProject.buildDiris deprecated. In current Gradle it is replaced byrootProject.layout.buildDirectory, so a modern clean task readsdelete(rootProject.layout.buildDirectory).- The Compose compiler is now a plugin. Since Kotlin 2.0, you apply
org.jetbrains.kotlin.plugin.composeand thecomposeOptions.kotlinCompilerExtensionVersionline is no longer required, because the compiler version tracks your Kotlin version. - KSP over kapt. Many annotation processors, Room included, now support KSP, which is faster than kapt. Where a library offers it, prefer
ksp("...")overkapt("...").
None of that changes how you translate Groovy into Kotlin. It changes what a brand-new file looks like, so treat the examples above as the migration path and this list as the destination.
Frequently Asked Questions
Why use Kotlin DSL instead of Groovy for Gradle? The Kotlin DSL gives you full IDE support: code completion, refactoring, and click-through navigation into the build API, because the script is statically typed. Groovy build scripts are dynamically typed, so the IDE can only guess. Since Kotlin DSL is now the default for new Android projects, using it also keeps your build consistent with the rest of the ecosystem.
What are the main syntax differences between Groovy and Kotlin build files?
Three rules cover most of it. Function calls need parentheses, so implementation 'lib' becomes implementation("lib"). Strings use double quotes, not single quotes. And property assignments need an equals sign, so compileSdk 31 becomes compileSdk = 31. A few APIs also have different names in Kotlin, like isMinifyEnabled instead of minifyEnabled.
Why do I get errors after converting even though the syntax looks right? Most of the time it is a stale sync cache, not your code. Run Build > Clean Project then Build > Rebuild Project, or File > Invalidate Caches / Restart. If the errors persist after that, look for a property missing its equals sign or a function call left without parentheses, which are the two mistakes that slip through most often.
Do I still need composeOptions.kotlinCompilerExtensionVersion?
Not on Kotlin 2.0 and later. The Compose compiler moved into a Gradle plugin, org.jetbrains.kotlin.plugin.compose, and its version follows your Kotlin version automatically. The kotlinCompilerExtensionVersion line shown in the older composeOptions example is only needed on older setups that predate the plugin.
Happy coding.
David Cruz davthecoder.com
Loading comments…