# General Gradle Best Practices

version 8.14.5-20260320042847+0000

## Use Kotlin DSL

Prefer the Kotlin DSL (`build.gradle.kts`) over the Groovy DSL (`build.gradle`) when authoring new builds or creating new subprojects in existing builds.

### Explanation

The Kotlin DSL offers several advantages over the Groovy DSL:

- Strict typing: IDEs provide better auto-completion and navigation with the Kotlin DSL.

- Improved readability: Code written in Kotlin is often easier to follow and understand.

- Single-language stack: Projects that already use Kotlin for production and test code don’t need to introduce Groovy just for the build.

Since Gradle 8.0, Kotlin DSL is the default for new builds created with `gradle init`. Android Studio also defaults to Kotlin DSL.

### References

- Kotlin DSL Primer

### Tags

`#kotlin-dsl`

## Use the Latest Minor Version of Gradle

Stay on the latest minor version of the major Gradle release you’re using, and regularly update your plugins to the latest compatible versions.

### Explanation

Gradle follows a fairly predictable, time-based release cadence. Only the latest minor version of the current and previous major release is actively supported.

We recommend the following strategy:

- Try upgrading directly to the latest minor version of your current major Gradle release.

- If that fails, upgrade one minor version at a time to isolate regressions or compatibility issues.

Each new minor version includes:

- Performance and stability improvements.

- Deprecation warnings that help you prepare for the next major release.

- Fixes for known bugs and security vulnerabilities.

Use the `wrapper` task to update your project:

    ./gradlew wrapper --gradle-version <version>

You can also install the latest Gradle versions easily using tools like SDKMAN! or Homebrew, depending on your platform.

### Plugin Compatibility

Always use the latest compatible version of each plugin:

- Upgrade Gradle before plugins.

- Test plugin compatibility using shadow jobs.

- Consult changelogs when updating.

Subscribe to the Gradle newsletter to stay informed about new Gradle releases, features, and plugins.

### References

- Upgrade Guide

### Tags

`#plugins`

## Apply Plugins Using the `plugins` Block

You should always use the `plugins` block to apply plugins in your build scripts.

### Explanation

The `plugins` block is the preferred way to apply plugins in Gradle. The plugins API allows Gradle to better manage the loading of plugins and it is both more concise and less error-prone than adding dependencies to the buildscript’s classpath explicitly in order to use the `apply` method.

It allows Gradle to optimize the loading and reuse of plugin classes and helps inform tools about the potential properties and values in extensions the plugins will add to the build script. It is constrained to be idempotent (produce the same result every time) and side effect-free (safe for Gradle to execute at any time).

### Example

#### Don’t Do This

build.gradle.kts

    buildscript {
        repositories {
            gradlePluginPortal() (1)
        }

        dependencies {
            classpath("com.google.protobuf:com.google.protobuf.gradle.plugin:0.9.4") (2)
        }
    }

    apply(plugin = "java") (3)
    apply(plugin = "com.google.protobuf") (4)

build.gradle

    buildscript {
        repositories {
            gradlePluginPortal() (1)
        }

        dependencies {
            classpath("com.google.protobuf:com.google.protobuf.gradle.plugin:0.9.4") (2)
        }
    }

    apply plugin: "java" (3)
    apply plugin: "com.google.protobuf" (4)

1 | Declare a Repository: To use the legacy plugin application syntax, you need to explicitly tell Gradle where to find a plugin.
2 | Declare a Plugin Dependency: To use the legacy plugin application syntax with third-party plugins, you need to explicitly tell Gradle the full coordinates of the plugin.
3 | Apply a Core Plugin: This is very similar using either method.
4 | Apply a Third-Party Plugin: The syntax is the same as for core Gradle plugins, but the version is not present at the point of application in your buildscript.

#### Do This Instead

build.gradle.kts

    plugins {
        id("java") (1)
        id("com.google.protobuf").version("0.9.4") (2)
    }

build.gradle

    plugins {
        id("java") (1)
        id("com.google.protobuf").version("0.9.4") (2)
    }

1 | Apply a Core Plugin: This is very similar using either method.
2 | Apply a Third-Party Plugin: You specify the version using method chaining in the `plugins` block itself.

### References

- Using Plugins

### Tags

`#structuring-builds`

## Do Not Use Internal APIs

Do not use APIs from a package where any segment of the package is `internal`, or types that have `Internal` or `Impl` as a suffix in the name.

### Explanation

Using internal APIs is inherently risky and can cause significant problems during upgrades. Gradle and many plugins (such as Android Gradle Plugin and Kotlin Gradle Plugin) treat these internal APIs as subject to unannounced breaking changes during any new Gradle release, even during minor releases. There have been numerous cases where even highly experienced plugin developers have been bitten by their usage of such APIs leading to unexpected breakages for their users.

If you require specific functionality that is missing, it’s best to submit a feature request. As a temporary workaround consider copying the necessary code into your own codebase and extending a Gradle public type with your own custom implementation using the copied code.

### Example

#### Don’t Do This

build.gradle.kts

    import org.gradle.api.internal.attributes.AttributeContainerInternal

    configurations.create("bad") {
        attributes {
            attribute(Usage.USAGE_ATTRIBUTE, objects.named<Usage>(Usage.JAVA_RUNTIME))
            attribute(Category.CATEGORY_ATTRIBUTE, objects.named<Category>(Category.LIBRARY))
        }
        val badMap = (attributes as AttributeContainerInternal).asMap() (1)
        logger.warn("Bad map")
        badMap.forEach { (key, value) ->
            logger.warn("$key -> $value")
        }
    }

build.gradle

    import org.gradle.api.internal.attributes.AttributeContainerInternal

    configurations.create("bad") {
        attributes {
            attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_RUNTIME))
            attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, Category.LIBRARY))
        }
        def badMap = (attributes as AttributeContainerInternal).asMap() (1)
        logger.warn("Bad map")
        badMap.each {
            logger.warn("${it.key} -> ${it.value}")
        }
    }

1 | Casting to `AttributeContainerInternal` and using `toMap()` should be avoided as it relies on an internal API.

#### Do This Instead

build.gradle.kts

    configurations.create("good") {
        attributes {
            attribute(Usage.USAGE_ATTRIBUTE, objects.named<Usage>(Usage.JAVA_RUNTIME))
            attribute(Category.CATEGORY_ATTRIBUTE, objects.named<Category>(Category.LIBRARY))
        }
        val goodMap = attributes.keySet().associate { (1)
            Attribute.of(it.name, it.type) to attributes.getAttribute(it)
        }
        logger.warn("Good map")
        goodMap.forEach { (key, value) ->
            logger.warn("$key -> $value")
        }
    }

build.gradle

    configurations.create("good") {
        attributes {
            attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_RUNTIME))
            attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, Category.LIBRARY))
        }
        def goodMap = attributes.keySet().collectEntries {
            [Attribute.of(it.name, it.type), attributes.getAttribute(it as Attribute<Object>)]
        }
        logger.warn("Good map")
        goodMap.each {
            logger.warn("$it.key -> $it.value")
        }
    }

1 | Implementing your own version of `toMap()` that only uses public APIs is a lot more robust.

### References

- Gradle API

### Tags

`#upgrades`