Lazy Evaluation of Gradle Properties

  • November 14, 2017
Table Of Contents

Writing Gradle build tasks is often easy and straight forward, but as soon as you start to write more generic tasks for multiple modules or projects it can get a little tricky.

Why Lazy Evaluation?

Recently I wrote a task to configure a docker build for different Java modules. Some of them are packaged as JAR and some as WAR artifacts. Now this configuration was not that complicated, but I really hate duplicating stuff. So I wondered how to write a generic configuration and let each module override some parts of this config? That’s where lazy property evaluation comes in very handy.

Lazy Evaluation of String Properties

Let’s check this simple project configuration, which logs the the evaluated properties to the console using the build-in Gradle Logger.

allprojects {
    version = '1.0.0'

    ext {
        artifactExt = "jar"
        dockerArtifact = "${name}-${version}.${artifactExt}"
    }
}

subprojects {
    task printArtifactName {
        doLast {
            logger.lifecycle "Artifact  ${dockerArtifact}"
        }
    }
}

project('A') {
    // using default configuration
}

project('B') {
    artifactExt = 'war'
}

The above code should do exactly what we want:

./gradlew printArtifactName
:A:printArtifactName
Artifact  A-1.0.0.jar
:B:printArtifactName
Artifact  B-1.0.0.jar

Wait, didn’t we override the default artifactExt property within module B? Gradle seems to ignore the overridden property!

Let’s modify the example task to get a deeper insight:

task printArtifactName {
    doLast {
        logger.lifecycle dockerArtifact
        logger.lifecycle artifactExt
    }
}
./gradlew printArtifactName
:A:printArtifactName
Artifact  A-1.0.0.jar
Extension jar
:B:printArtifactName
Artifact  B-1.0.0.jar
Extension war

Looks like the property artifactExt gets overridden correctly. The problem is caused by the evaluation time of the property dockerArtifact. Within Gradles configuration phase dockerArtifact gets evaluated directly, but at that time artifactExt is defined with it’s default value jar. Later when configuring project B, dockerArtifact is already set and overriding artifactExt does not affect the value of dockerArtifact anymore. So we have to tell Gradle to evaluate the property artifactExt at execution time.

We can do that by turning the property into a Closure like that:

dockerArtifact = "${name}-${version}.${-> artifactExt}"

Now Gradle evaluates name and version properties eagerly but artifactExt gets evaluated lazily each time dockerArtifact is used. Running the modified code again gives us the expected result:

./gradlew printArtifactName
:A:printArtifactName
Artifact  A-1.0.0.jar
Extension jar
:B:printArtifactName
Artifact  B-1.0.0.war
Extension war

This simple hack can come in quite handy, but can only be used within Groovy Strings, as it uses Groovys build-in Lazy String Evaluation. Note that Groovy Strings are those Strings wrapped in double quotes, whereas regular Java Strings are wrapped in single quotes.

Lazy Evaluation of non-String Properties

Using Closures you can also use lazy evaluation for other property types like shown below.

Let’s define another property called maxMemory as a Closure.

allprojects {
    version = '1.0.0'

    ext {
        artifactExt = "jar"
        dockerArtifact = "${name}-${version}.${-> artifactExt}"

        minMemory = 128
        // use a Closure for maxMemory calculation
        maxMemory = { minMemory * 2 }
    }
}

subprojects {
    task printArtifactName {
        doLast {
            logger.lifecycle "Artifact  ${dockerArtifact}"
            logger.lifecycle "Extension ${artifactExt}"
            logger.lifecycle "Min Mem   ${minMemory}"
            // running maxMemory Closure by invoking it
            logger.lifecycle "Max Mem   ${maxMemory()}"
        }
    }
}

project('B') {
    artifactExt = 'war'
    minMemory = 512
}

As you can see the real difference to lazy String evaluation is how the closure gets invoked at execution time. We invoke the Closure by adding parenthesis to the property name.

Running the modified code again gives us the expected result:

./gradlew printArtifactName
:A:printArtifactName
Artifact  A-1.0.0.jar
Extension jar
Min Mem   128
Max Mem   256
:B:printArtifactName
Artifact  B-1.0.0.war
Extension war
Min Mem   512
Max Mem   1024

As you can see lazy evaluation of properties is really simple and allows more complex configurations without the need of duplicating code.

Written By:

Matthias Balke

Written By:

Matthias Balke

Recent Posts

Guide to JUnit 5 Functional Interfaces

In this article, we will get familiar with JUnit 5 functional interfaces. JUnit 5 significantly advanced from its predecessors. Features like functional interfaces can greatly simplify our work once we grasp their functionality.

Read more

Getting Started with Spring Security and JWT

Spring Security provides a comprehensive set of security features for Java applications, covering authentication, authorization, session management, and protection against common security threats such as CSRF (Cross-Site Request Forgery).

Read more

Creating and Publishing an NPM Package with Automated Versioning and Deployment

In this step-by-step guide, we’ll create, publish, and manage an NPM package using TypeScript for better code readability and scalability. We’ll write test cases with Jest and automate our NPM package versioning and publishing process using Changesets and GitHub Actions.

Read more