Simpler Kotlin DSL Property Assignment

One of the improvements that made making Kotlin DSL the default for new Gradle builds possible was a simpler way to assign values with lazy properties.

In this blog post, we will explain what lazy property assignment is and how you can take advantage of the new syntax in the latest Gradle release.

What is the lazy property API?

The lazy property API is a Gradle feature that delays the calculation of a property’s value until its required. This is the recommended way of writing properties for extensions and tasks.

Some of the provided benefits include:

  • Solving configuration ordering issues (no more Project.afterEvaluate())
  • Tracking dependencies automatically (no need for Task.dependsOn())
  • Providing standardized access (no custom behavior in setters or getters)
  • Reducing boilerplate for property declarations (no field and a setter, just a getter)

To use the lazy property API in a custom task, you replace primitive types with a property type. e.g.,

abstract class MyTask : DefaultTask() {
    @get:Input
    var myProperty: String? = null

    @TaskAction
    fun run() {
        // Skipped for brevity
    }
}

Becomes:

abstract class MyTask : DefaultTask() {
    @get:Input
    abstract val myProperty: Property<String>

    @TaskAction
    fun run() {
        // Skipped for brevity
    }
}

Here comes trouble!

While lazy properties provide several benefits, they may make configuring properties with the Kotlin DSL a bit verbose.

In Groovy DSL, lazy properties can be assigned with a simple =:

tasks.register("taskName", MyTask) {
    myProperty = "Constant string value"
}

However, in Kotlin DSL, assigning a value to a property required the use of a verbose set(…) method:

tasks.register<MyTask>("taskName") {
    myProperty.set("Constant string value")
}

Users have clearly wanted a simpler syntax that looked more declarative. The request for a concise and statically typed Property<T> assignment is one of the most highly voted GitHub issues. As the Kotlin language did not support overloading the assign operator, there were not many good choices to improve this situation.

Because of the verbosity, some plugin authors invented custom setters. These setters were implemented in many different ways, further degrading the user experience.

We didn’t want to force a more verbose DSL when using lazy properties or discourage the use of lazy properties in all plugins.

An answer for simple assignment

For a task or extension that uses a lazy property type (e.g. Property), Gradle needs a simple way to assign values.

As of Gradle 8.2, it is now possible to use the = operator to assign values to Property types as a direct alternative to the set(T) method in Kotlin DSL scripts.

The verbose option:

tasks.register<MyTask>("taskName") {
    // Using the set() method call
    myProperty.set("Constant string value")
}

becomes:

tasks.register<MyTask>("taskName") {
    // Using lazy property assignment
    myProperty = "Constant string value"
}

This syntax also supports assigning lazy values, like the set(Provider<T>) method:

tasks.register<MyTask>("taskName") {
    // otherTask.map() returns lazy value `Provider<T>`
    myProperty = otherTask.map { it.otherProperty }
}

Kotlin DSL now looks much better. As a bonus, this makes the migration from Groovy DSL to Kotlin DSL easier as it reduces inconsistencies between the two DSLs.

Lazy property assignment works for getter-only properties of type Property or ConfigurableFileCollection. We do not recommended that plugin authors implement custom setters for properties with these types.

A joint effort with JetBrains

Implementing lazy property assignment would not have been possible without the close collaboration with the Kotlin team.

Together, we implemented a Kotlin compiler plugin that provides an option to overload the = assignment operator for final properties. This is similar to what the Kotlin language supports with other operators like +=. Since writing such a plugin required changes in the Kotlin compiler, we were not able to implement this change until now.

Internally, Gradle uses the new compiler plugin and provides methods that overload the = operator for lazy types:

fun <T> Property<T>.assign(value: T?) {
    this.set(value)
}

Because the feature is written as a Kotlin compiler plugin, we also get all the benefits of the Kotlin language including static typing, discoverability, and great IDE integration.

We can also proudly say that this was the first externally contributed Kotlin language feature! The new plugin ships directly with Kotlin.

Thank you to the JetBrains team for their support and collaboration on this feature.

Try it out in Gradle 8.2

Gradle added lazy property assignment support in Gradle 8.1 as an opt-in feature. Gradle 8.2 enables this by default, so you can simply use it in your *.gradle.kts scripts.

It’s important to note that the feature is still marked as incubating. Changes to the feature are possible, although unlikely. We don’t recommend using this feature in published plugins until it’s marked as stable (likely in Gradle 8.3).

Lazy property assignment is supported in IntelliJ 2022.3 (2023.1.1 or newer is recommended) and Android Studio Giraffe (or newer).

There are three known issues with IDE integration:

KT-56941 and KT-56221 were fixed with IntelliJ 2023.1.1.

KTIJ-24390 will be fixed in a future release.

Conclusion

Lazy property assignment:

  • Reduces the verbosity of Kotlin DSL
  • Standardizes task and extension property access
  • Simplifies usage of lazy property API
  • Helps users migrate from Groovy to Kotlin

If you want to learn more about lazy property assignment with Kotlin, check out the Gradle Kotlin DSL primer.

If you are migrating an existing project from Groovy DSL to Kotlin DSL, be sure to read the migration guide and consider using the lazy property API.

Join the community chat (#kotlin-dsl channel) and forum to ask questions, share experiences, and contribute to the development of Kotlin DSL. Your feedback and contributions are highly valued and essential for shaping the future of the Gradle Build Tool.