Following up on my previous post about Gradle, I’ll now dive into Gradle properties — we’ll discuss what kinds of properties there are, how we can access them and what they’re used for. If you haven’t read the previous post, I encourage you to do so, as this post relies on some bits that I explain there.

What are properties?

Groovy documentation defines a property as a combination of a private field and a getter/setter. Usually in Java we would declare a private field, a getter and a setter explicitly. In some programming languages though, this practice is wrapped in a concept of properties. In short, when declaring a property, a private field, a getter, and a setter (if the property is not final) are generated. By default, getters and setters just read/write to the backing field, but they can also be customised, so things like validation can be added.

Syntax

Here’s a comparison of Java code (without properties), and equivalent Kotlin and Groovy declarations using property syntax:

The same written in Kotlin:

And the same in Groovy:

This lengthy introduction to properties is only here so we know that:

Property access syntax is similar to field access syntax in Java, but they shouldn’t be confused

Accessing a property is actually translated to a method call, and this method can do things

Dynamic properties

One more important thing in Groovy that affects Gradle heavily is that Groovy is a dynamic language. In my previous post I briefly mentioned the dynamic method access, but Groovy actually offers the dynamic property access as well. This means that sometimes even though a property with certain name wasn’t declared in the object, such property can still be accessed — provided the said object offers dynamic property access. This is important, since quite a lot of properties that we access in Gradle are added dynamically. Just for fun, let’s have a look at how we’d use dynamic properties in Groovy (this is what Gradle uses under the hood):

As we can see, even though meaningOfLife doesn’t exist, the code above not only compiles, but also doesn’t crash at runtime. This mechanism is used a lot in Gradle scripts.

Properties in Gradle

Now that we know what the properties are, what their syntax is, and that some of them may be added dynamically, let’s have a look at different properties that we can encounter.

Class properties

This one is pretty obvious — if a class, say Project , declares a property, then it can be accessed.

Extension properties

Extension properties are ad-hoc properties that can be added to any object that implements ExtensionAware . In other words, every ExtensionAware object can be extended with an arbitrary extension object with the given name. This is done via extensions property of type ExtensionContainer present in every ExtensionAware object. Added extension objects can be then accessed using property syntax. Here’s an example of how that would work with Project class, which implements ExtensionAware :

First thing to note is that the Project class does not normally have a person property. It’s added dynamically when calling extensions.create method. Second thing to note is that for this newly created property we can use both normal property syntax (assigning the name) as well as namespace method, which allows us to configure extension object using a closure. This is what we usually see when configuring android extension. Both Project and Task are ExtensionAware .

Talking about methods in a post about properties may be confusing, but the point is that person is an extension, and extensions can both be accessed as properties, or configured via namespace method.

We know extensions very well — for example Android Gradle plugin adds android extension of AppExtension type. We usually use method syntax to configure it, but we can access it using property syntax as well (e.g. android.defaultConfig.compileSdkVersion = 24 ).

Extra properties

Extra properties is a special extension of type ExtraPropertiesExtension added with name ext to all ExtensionAware objects. We can think of it as a container of arbitrary key-value pairs, just with special syntax:

As we see, we can create and access keys in extra properties using both property syntax (prefer that) and accessor syntax. Here’s the thing, though: we can skip the ext part, and access a property directly from ExtensionAware object:

The above statements aren’t always equivalent — we’ll see why later.

Another thing to remember is that properties passed with -P command line options or added to gradle.properties file are added to the extra properties extension. This is why (and how) we can access them — they’re not in a separate scope, or considered separately. They’re simply added to one of existing scopes. As we’ll see later, this is why project.name property can’t be set by adding it in gradle.properties , but for example project.personName can.

Convention properties

Convention properties is an older version of extension properties, now mostly replaced by it. The difference is that convention properties are immutable (albeit assignable), and are lazily evaluated. We’ll not dig into details as extensions are much more prevalent in Gradle now.

Tasks of the project

Simply put, when we’re in Project class, its tasks are accessible as properties. For example project.assembleDebug is equivalent to project.tasks.getByName('assembleDebug') .

Resolving properties in build.gradle files

We now see that properties can come from many places — so now the questions is: what happens if there’s a collision? Supposed we created an extra property name . Which name will we get when we call project.name - name defined in Project class, or the one in extra property? Fortunately, there’s a strict order of scopes in which properties are searched. Remembering that build.gradle files are executed against a Project instances, the scopes which are searched for properties are:

Project object — if there’s a property with a given name declared in Project class, it’s used. Whether it’s readable or writable is determined by the presence of corresponding getter or setter method.

— if there’s a property with a given name declared in class, it’s used. Whether it’s readable or writable is determined by the presence of corresponding getter or setter method. Extra properties — from ext extension; these are both readable and writable

— from extension; these are both readable and writable Extensions — read-only, usually added by plugins

— read-only, usually added by plugins Convention properties

Extra properties, extensions and convention properties of the parent project (recursively, up to the root project), but in the read-only scope

Examples

Let’s have a look at some code that we could write in build.gradle files. Again, build.gradle means that the code is executed on Project instance, and Project is ExtensionAware .

[1] Here we write extra property called personName . This is the first time we do it, so this actually creates a mapping for this property’s name in ext object.

[2] Here we access extra property personName explicitly from ext namespace

[3] And here we access it second time, but this time without the namespace. This works because there’s no personName in Project class, and extra properties are next in order to look for a missing property. Notice that we wouldn’t be able to do that before [1], as the property wouldn’t be defined yet.

Extensions example

Let’s have a look at extensions now:

person in [1] is our PersonExtension , so we can access its name property. [2] defines extra property person , so now [3] will print “Tim” , because extra properties have higher priority than extensions. Notice that after we write [2], we can no longer write person.name , because person will now always refer to extra property, and not an extension (and this property is a String , not a PersonExtension )! If we wanted to change the name in person extension, we now have to refer to extension explicitly directly: this.extensions.person.name = "Molly" .

Reading properties in child project

If we have two files: root project’s build.gradle , and child project’s build.gradle like this:

[1] will print out “Tim” — because person is not found in the current project (it’s not in Project class, there’s no extra person in the child project, no extension like a convention or a task), the parent project is searched - and person is finally found in its extra properties.

[2] will fail, because ext refers to child project’s extra properties, and this one doesn’t have person defined. If we comment this line, then:

[3] will define person in child project’s extras

[4] and [5] Will now print out “John”, because child project’s extras now contain person property, and they’re searched before parent’s extras

[6] will print “Tim”, because now we explicitly ask for parent’s extra properties

Writing properties in a child project

Let’s take the same root project’s build.gradle :

and the following child project’s build.gradle file:

[1] will fail — this is because the person property comes from parent project, so it’s read-only.

[2] and [3] Will work, and will assign “John” to the person property in root project’s extras. This is because now we’re directly operating on parent project's, where its extra properties are writable (or on extra extension, in which everything is readable and writable).

This may be confusing since previously it was stated that parent properties are accessed in read-only scope. This is true — when accessing property from parent using property access syntax on child project, then property is read-only. In [2] and [3] property from parent is accessed using property access on parent project, so it’s writable.

[4] will define person in child project’s extras.

[5] and [6] Will assign “Tim” to child project’s person extra property. This differs from [1] in than this person is child project’s one, not parent’s.

[7] and [8] Will print “Tim” — both lines refer to child project’s extra person property

[9] will print “John” — this time we explicitly ask for parent.

Properties vs variables

It’s also important to remember the distinction between properties (which can be dynamically resolved by Gradle) and variables (which are part of Groovy language). If we want to declare a propery within a Project (for example to be used by child project), then we have to use ext property. But if we want to declare a variable to be used in the script, then we should use standard Groovy syntax - using def keyword, or by specifying type explicitly. But then what we declared is only a variable in the script, and it precedes all the scopes that we’ve talked about.

Here [1] will print “Molly”, because a script variable has precedence over properties. When in [2] we modify personName , we refer to the variable, so [3] will print “Tim”. [4] addresses extra property explcitly, so it will print “John”.

Summary

That’s about it when it comes to Gradle properties. Hopefully, this brings you a step closer to fully understanding Gradle scripts and the ability to confidently write one by yourself!

P.S. Again many thanks to Gradle team and Android team at Tooploox for proofreading the post, and to Ania Langiewicz for the wonderful feature graphic!