# Gradle Managed Types

version 9.3.0-20251108020513+0000

Gradle Managed Types are the building blocks for writing modern, efficient, and cache-friendly build logic.

When writing build logic, you’ll often reach for standard Groovy or Kotlin types. However, Gradle provides its own managed types that are lazy, making them far better suited for build logic.

These types enable:

- Incremental builds

- Build cache support

- Configuration cache compatibility

- Accurate tracking of task inputs and outputs

For example, instead of using a `String`, you should use a `Property<String>`:

build.gradle

    tasks.register("demoTask") {
        // Eager evaluation: happens immediately during configuration
        def eagerMessage = "Hello from eager"

        // Lazy evaluation: will only be computed if the task runs during execution
        def lazyMessage = objects.property(String)
        lazyMessage.set(providers.provider {
            return "Hello from lazy"
        })

        // This block runs during the configuration phase (when the build script is loaded)
        println ">> DURING CONFIGURATION"
        println ">>> eagerMessage type: ${eagerMessage.getClass()}"
        println ">>> eagerMessage value: $eagerMessage"
        println ">>> lazyMessage type: ${lazyMessage.getClass()}"
        println ">>> lazyMessage value: $lazyMessage"

        // This block runs during the execution phase (when the task is actually run)
        doLast {
            println ">> DURING EXECUTION"
            println ">>> eagerMessage type: ${eagerMessage.getClass()}"
            println ">>> eagerMessage value: $eagerMessage"
            // Now the provider is evaluated and the actual value is computed and printed
            println ">>> lazyMessage type: ${lazyMessage.getClass()}"
            println ">>> lazyMessage value: ${lazyMessage.get()}"
        }
    }

    $ ./gradlew demoTask

    >> DURING CONFIGURATION
    >>> eagerMessage type: class kotlin.String
    >>> eagerMessage value: Hello from eager
    >>> lazyMessage type: class org.gradle.api.internal.provider.DefaultProperty
    >>> lazyMessage value: property(java.lang.String, map(java.lang.String provider(?) check-type()))

    > Task :app:demoTask
    >> DURING EXECUTION
    >>> eagerMessage type: class kotlin.String
    >>> eagerMessage value: Hello from eager
    >>> lazyMessage type: class org.gradle.api.internal.provider.DefaultProperty
    >>> lazyMessage value: Hello from lazy

This example showcases that:

- Eager values are computed immediately when the script is loaded (configuration phase).

- Lazy values (like `Property` or `Provider`) defer computation until the task is executed (execution phase).

It’s always best to ensure Gradle evaluates task logic only during the execution phase to avoid wasting time during configuration. Using lazy types and values in Gradle ensures builds are fast, efficient, and compatible with performance features like incremental builds, the Build Cache, and the Configuration Cache.

## Common Managed Types

Here is a list of common Gradle managed types you can use in your build logic:

Type | Purpose | Use-Case
--- | --- | ---
`Property<T>` | Lazy scalar value (e.g., a version number) | A version string
`ListProperty<T>` | Lazy list of values (e.g., compiler args) | A list of compiler args
`MapProperty<K, V>` | Lazy map of values (e.g., environment vars) | A map of Maven Pom properties
`RegularFileProperty` | Lazy reference to a single file (input or output) | A single file input/output
`DirectoryProperty` | Lazy reference to a directory (input or output) | A destination directory
`Provider<T>` | Lazily evaluated, read-only value (e.g., task output) | A dependency on task output

Gradle distinguishes between eager and lazy evaluation to control when values are computed and how they participate in up-to-date checks, caching, and task wiring. Let’s review the differences one more time:

## Eager Evaluation

- Happens immediately during the configuration phase.

- Values are resolved as soon as the line is executed.

- Prevents Gradle from optimizing build logic.

    val version = project.version.toString()         // evaluated now
    val file = File("build/output.txt")              // evaluated now
    myTask.outputFile.set(file)

These eager values are resolved even if the task isn’t run, breaking configuration cache compatibility and reducing build performance.

## Lazy Evaluation

- Defers computation until it’s actually needed (usually during task execution).

- Enables Gradle to apply caching, parallel execution, and configuration avoidance.

- Fully supports configuration cache and incremental builds.

    val outputFile: RegularFileProperty = project.objects.fileProperty()
    outputFile.set(layout.buildDirectory.file("output.txt"))    // evaluated later

Here, nothing is resolved immediately—Gradle delays evaluation until it’s actually required.

Let’s take a look at an example.

## Example

This task will break the Configuration Cache due to eager access:

    // ❌ Not Configuration Cache compatible
    tasks.register("printVersion") {
        doLast {
            val version = project.version.toString()
            println("Version is $version")
        }
    }

The fix: use a lazy `Provider` or a `Property<String>`:

    // ✅ Configuration Cache compatible
    tasks.register("printVersion") {
        val version: Property<String> = project.objects.property(String::class.java)
        version.set(project.version.toString())
        doLast {
            println("Version is ${version.get()}")
        }
    }

Next Step: Learn about Declaring and Managing Dependencies >>