Skip to content
davthecoder
From Gradle to Gradle.kts: The definitive Groovy to Kotlin conversion guide.
tech

From Gradle to Gradle.kts: The definitive Groovy to Kotlin conversion guide.

By David Cruz Anaya, Independent Senior Mobile Engineer (Android, Kotlin Multiplatform, Rust)

Updated 11 July 2026
AndroidMobile DevelopmentKotlinRustPerformance
Share:

Gradle to Gradle.kts migration guide banner

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.

  1. Rename each build.gradle file to build.gradle.kts. Start with the project-level file, then the module (app) file.
  2. In the project-level file, add gradlePluginPortal() to buildscript.repositories, wrap every classpath dependency in parentheses, and switch to double quotes.
  3. Rewrite the clean task using tasks.register, since the Groovy task clean(type: Delete) syntax does not exist in Kotlin.
  4. In the module file, convert plugins, dependencies, and the android block: add parentheses to functions, double quotes to strings, and an equals sign to every property.
  5. Replace API names that differ in Kotlin, such as minifyEnabled with isMinifyEnabled and debug { } with getByName("debug") { }.
  6. 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() to buildscript > repositories.
  • Add parentheses to the classpath dependencies and use double quotes (") instead of single quotes (').
  • Refactor the Gradle clean task: use the tasks variable with the register extension function, and pass the task name, the type class, and a lambda action. Add parentheses after delete.
// 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, keep id but add parentheses and double quotes.
  • For Kotlin plugins, replace id with the kotlin("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 buildTypes are declared with getByName("debug") instead of a bare debug { }.
  • minifyEnabled becomes isMinifyEnabled.
  • Every property gets an equals sign, every string gets double quotes, and functions like proguardFiles get 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.kts files out of the box, so for greenfield work there is nothing to convert.
  • Version catalogs. New projects also ship a gradle/libs.versions.toml catalog, and dependencies are referenced as implementation(libs.androidx.core.ktx) instead of a hardcoded coordinate string. It centralises versions across modules.
  • rootProject.buildDir is deprecated. In current Gradle it is replaced by rootProject.layout.buildDirectory, so a modern clean task reads delete(rootProject.layout.buildDirectory).
  • The Compose compiler is now a plugin. Since Kotlin 2.0, you apply org.jetbrains.kotlin.plugin.compose and the composeOptions.kotlinCompilerExtensionVersion line 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("...") over kapt("...").

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

Share:

Comments

Loading comments…