Variants represent different versions or aspects of a component, like api vs implementation or debug vs release. Attributes define which variant is selected based on the consumer’s requirements.

For example, a library may have an api and an implementation variant. Here, the consumer wants an external implementation variant:

configurations {
    implementation {
        attributes {
            attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.EXTERNAL))
        }
    }
}

For example, a build might have debug and release variants. This selects the debug variant based on the attribute.

configurations {
    compileClasspath {
        attributes {
            attribute(TargetConfiguration.TARGET_ATTRIBUTE, objects.named("debug"))
        }
    }
}

Attributes help Gradle match the right variant by comparing the requested attributes with what’s available:

attribute(TargetConfiguration.TARGET_ATTRIBUTE, objects.named("debug"))

This sets the TargetConfiguration.TARGET_ATTRIBUTE to "debug", meaning Gradle will attempt to resolve dependencies that have a "debug" variant, instead of other available variants (like "release").

To understand how Gradle’s dependency management engine works to select the best matching variant, see our Understanding Variant Selection chapter.

Standard attributes defined by Gradle

As a user of Gradle, attributes are often hidden as implementation details. But it might be useful to understand the standard attributes defined by Gradle and its core plugins.

As a plugin author, these attributes, and the way they are defined, can serve as a basis for building your own set of attributes in your ecosystem plugin.

Ecosystem-independent standard attributes

Attribute name Description Values compatibility and disambiguation rules

org.gradle.usage

Indicates main purpose of variant

Usage values built from constants defined in Usage

Following ecosystem semantics (e.g. java-runtime can be used in place of java-api but not the opposite)

org.gradle.category

Indicates the category of this software component

Category values built from constants defined in Category

Following ecosystem semantics (e.g. library is default on the JVM, no compatibility otherwise)

org.gradle.libraryelements

Indicates the contents of a org.gradle.category=library variant

LibraryElements values built from constants defined in LibraryElements

Following ecosystem semantics(e.g. in the JVM world, jar is the default and is compatible with classes)

org.gradle.docstype

Indicates the contents of a org.gradle.category=documentation variant

DocsType values built from constants defined in DocsType

No default, no compatibility

org.gradle.dependency.bundling

Indicates how dependencies of a variant are accessed.

Bundling values built from constants defined in Bundling

Following ecosystem semantics (e.g. in the JVM world, embedded is compatible with external)

org.gradle.verificationtype

Indicates what kind of verification task produced this output.

VerificationType values built from constants defined in VerificationType

No default, no compatibility

When the Category attribute is present with the incubating value org.gradle.category=verification on a variant, that variant is considered to be a verification-time only variant.

These variants are meant to contain only the results of running verification tasks, such as test results or code coverage reports. They are not publishable, and will produce an error if added to a component which is published.

Attribute name Description Values compatibility and disambiguation rules

org.gradle.status

Component level attribute, derived

Based on a status scheme, with a default one existing based on the source repository.

Based on the scheme in use

JVM ecosystem specific attributes

In addition to the ecosystem independent attributes defined above, the JVM ecosystem adds the following attribute:

Attribute name Description Values compatibility and disambiguation rules

org.gradle.jvm.version

Indicates the JVM version compatibility.

Integer using the version after the 1. for Java 1.4 and before, the major version for Java 5 and beyond.

Defaults to the JVM version used by Gradle, lower is compatible with higher, prefers highest compatible.

org.gradle.jvm.environment

Indicates that a variant is optimized for a certain JVM environment.

Common values are standard-jvm and android. Other values are allowed.

The attribute is used to prefer one variant over another if multiple are available, but in general all values are compatible. The default is standard-jvm.

org.gradle.testsuite.name

Indicates the name of the TestSuite that produced this output.

Value is the name of the Suite.

No default, no compatibility

org.gradle.testsuite.target.name

Indicates the name of the TestSuiteTarget that produced this output.

Value is the name of the Target.

No default, no compatibility

org.gradle.testsuite.type

Indicates the type of test suite (unit test, integration test, performance test, etc.)

TestSuiteType values built from constants defined in TestSuiteType or other custom values for user-defined test suite types.

No default, no compatibility

The JVM ecosystem also contains a number of compatibility and disambiguation rules over the different attributes. The reader willing to know more can take a look at the code for org.gradle.api.internal.artifacts.JavaEcosystemSupport.

Native ecosystem specific attributes

In addition to the ecosystem independent attributes defined above, the native ecosystem adds the following attributes:

Attribute name Description Values compatibility and disambiguation rules

org.gradle.native.debuggable

Indicates if the binary was built with debugging symbols

Boolean

N/A

org.gradle.native.optimized

Indicates if the binary was built with optimization flags

Boolean

N/A

org.gradle.native.architecture

Indicates the target architecture of the binary

MachineArchitecture values built from constants defined in MachineArchitecture

None

org.gradle.native.operatingSystem

Indicates the target operating system of the binary

OperatingSystemFamily values built from constants defined in OperatingSystemFamily

None

Gradle plugin ecosystem specific attributes

For Gradle plugin development, the following attribute is supported since Gradle 7.0. A Gradle plugin variant can specify compatibility with a Gradle API version through this attribute.

Attribute name Description Values compatibility and disambiguation rules

org.gradle.plugin.api‑version

Indicates the Gradle API version compatibility.

Valid Gradle version strings.

Defaults to the currently running Gradle, lower is compatible with higher, prefers highest compatible.

Using a standard attribute

For this example, let’s assume you are creating a library with different variants for different JVM versions.

lib/build.gradle.kts
plugins {
    id("java-library")
}

configurations {
    named("apiElements") {
        attributes {
            attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 17)
        }
    }
}
lib/build.gradle
plugins {
    id 'java-library'
}

configurations {
    apiElements {
        attributes {
            attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 17)
        }
    }
}

In the consumer project (that uses the library), you can specify the JVM version attribute when declaring dependencies.

consumer/build.gradle.kts
plugins {
    id("application")
}

dependencies {
    implementation(project(":lib")) {
        attributes {
            attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 17)
        }
    }
}
consumer/build.gradle
plugins {
    id 'application'
}

dependencies {
    implementation(project(':lib')) {
        attributes {
            attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, 17)
        }
    }
}

By defining and using the JVM version attribute, you ensure that your library and its consumers are compatible with the specified JVM version. Essentially, this ensures that Gradle resolves to the variant that matches the desired JVM version.

Viewing and debugging attributes

The dependencyInsight task is useful for inspecting specific dependencies and their attributes, including how they are resolved:

$ ./gradlew dependencyInsight --configuration compileClasspath --dependency com.example:your-library

> Task :dependencyInsight

com.example:your-library:1.0 (compileClasspath)
   variant "apiElements" [
      org.gradle.api.attributes.Attribute: org.gradle.api.attributes.Usage = [java-api]
      org.gradle.api.attributes.Attribute: org.gradle.api.attributes.Usage = [java-runtime]
      org.gradle.api.attributes.Attribute: org.gradle.api.attributes.JavaLanguageVersion = [1.8]
   ]
   variant "runtimeElements" [
      org.gradle.api.attributes.Attribute: org.gradle.api.attributes.Usage = [java-runtime]
      org.gradle.api.attributes.Attribute: org.gradle.api.attributes.JavaLanguageVersion = [1.8]
   ]

   Selection reasons:
      - By constraint from configuration ':compileClasspath'
      - Declared in build.gradle.kts

   Resolved to:
      com.example:your-library:1.0 (runtime)

   Additional Information:
      - Dependency declared in the 'implementation' configuration
      - No matching variants found for the requested attributes in the 'compileClasspath' configuration

Declaring custom attributes

When extending Gradle with custom attributes, it’s important to consider their long-term impact, especially if you plan to publish libraries. Custom attributes allow you to integrate variant-aware dependency management in your plugin, but libraries using these attributes must also ensure consumers can interpret them correctly. This is typically done by applying the corresponding plugin, which defines compatibility and disambiguation rules.

If your plugin is publicly available and libraries are published to public repositories, introducing new attributes becomes a significant responsibility. Published attributes must remain supported or have a compatibility layer in future versions of the plugin to ensure backward compatibility.

Here’s an example of declaring and using custom attributes in a Gradle plugin:

lib/build.gradle.kts
// Define a custom attribute
val myAttribute = Attribute.of("com.example.my-attribute", String::class.java)

configurations {
    create("myConfig") {
        // Set custom attribute
        attributes {
            attribute(myAttribute, "special-value")
        }
    }
}

dependencies {
    // Apply the custom attribute to a dependency
    add("myConfig","com.google.guava:guava:31.1-jre") {
        attributes {
            attribute(myAttribute, "special-value")
        }
    }
}
lib/build.gradle
// Define a custom attribute
def myAttribute = Attribute.of("com.example.my-attribute", String)

// Create a custom configuration
configurations {
    create("myConfig") {
        // Set custom attribute
        attributes {
            attribute(myAttribute, "special-value")
        }
    }
}

dependencies {
    // Apply the custom attribute to a dependency
    add("myConfig", "com.google.guava:guava:31.1-jre") {
        attributes {
            attribute(myAttribute, "special-value")
        }
    }
}

In this example: - A custom attribute my-attribute is defined. - The attribute is set on a custom configuration (myConfig). - When adding a dependency, the custom attribute is applied to match the configuration.

If publishing a library with this attribute, ensure that consumers apply the plugin that understands and handles my-attribute.

Creating attributes in a build script or plugin

Attributes are typed. An attribute can be created via the Attribute<T>.of method:

build.gradle.kts
// An attribute of type `String`
val myAttribute = Attribute.of("my.attribute.name", String::class.java)
// An attribute of type `Usage`
val myUsage = Attribute.of("my.usage.attribute", Usage::class.java)
build.gradle
// An attribute of type `String`
def myAttribute = Attribute.of("my.attribute.name", String)
// An attribute of type `Usage`
def myUsage = Attribute.of("my.usage.attribute", Usage)

Attribute types support most Java primitive classes; such as String and Integer. Or anything extending org.gradle.api.Named.

Attributes should always be declared in the attribute schema found on the dependencies handler:

build.gradle.kts
dependencies.attributesSchema {
    // registers this attribute to the attributes schema
    attribute(myAttribute)
    attribute(myUsage)
}
build.gradle
dependencies.attributesSchema {
    // registers this attribute to the attributes schema
    attribute(myAttribute)
    attribute(myUsage)
}

Registering an attribute with the schema is required in order to use Compatibility and Disambiguation rules that can resolve ambiguity between multiple selectable variants during Attribute Matching.

Each configuration has a container of attributes. Attributes can be configured to set values:

build.gradle.kts
configurations {
    create("myConfiguration") {
        attributes {
            attribute(myAttribute, "my-value")
        }
    }
}
build.gradle
configurations {
    myConfiguration {
        attributes {
            attribute(myAttribute, 'my-value')
        }
    }
}

For attributes which type extends Named, the value of the attribute must be created via the object factory:

build.gradle.kts
configurations {
    "myConfiguration" {
        attributes {
            attribute(myUsage, project.objects.named(Usage::class.java, "my-value"))
        }
    }
}
build.gradle
configurations {
    myConfiguration {
        attributes {
            attribute(myUsage, project.objects.named(Usage, 'my-value'))
        }
    }
}

Dealing with attribute matching

In Gradle, attribute matching and attribute disambiguation are key mechanisms for resolving dependencies with varying attributes.

Attribute matching allows Gradle to select compatible dependency variants based on predefined rules, even if an exact match isn’t available. Attribute disambiguation, on the other hand, helps Gradle choose the most suitable variant when multiple compatible options exist.

Attribute compatibility rules

Attributes let the engine select compatible variants. There are cases where a producer may not have exactly what the consumer requests but has a variant that can be used.

lib/build.gradle.kts
lib/build.gradle
  1. Attribute Definition: Define the attribute you want to apply compatibility rules to. In this case, JavaLanguageVersion.

  2. Register Compatibility Rule: Use the attributeMatchingStrategy to specify how to handle compatibility for the defined attribute. For instance, you can define which versions of the attribute are compatible.

  3. Compatibility Logic: Specify the compatibility logic inside the rule. You can define specific versions or attributes that are considered compatible or incompatible.

Gradle provides attribute compatibility rules that can be defined for each attribute. The role of a compatibility rule is to explain which attribute values are compatible based on what the consumer asked for.

Attribute compatibility rules have to be registered via the attribute matching strategy that you can obtain from the attributes schema.

Attribute disambiguation rules

When multiple variants of a dependency are compatible with the consumer’s requested attributes, Gradle needs to decide which variant to select. This process of determining the "best" candidate among compatible options is called attribute disambiguation.

In Gradle, different variants might satisfy the consumer’s request, but not all are equal. For example, you might have several versions of a library that are compatible with a Java version requested by the consumer. Disambiguation helps Gradle choose the most appropriate one based on additional criteria.

You can define disambiguation rules to guide Gradle in selecting the most suitable variant when multiple candidates are found. This is done by implementing an attribute disambiguation rule:

import org.gradle.api.attributes.Attribute
import org.gradle.api.attributes.AttributeMatchingStrategy

// Define custom attribute
val javaLanguageVersion = Attribute.of("org.gradle.jvm.version", String::class.java)

// Register disambiguation rules
configurations.all {
    attributes {
        // Define the attribute matching strategy
        attribute(javaLanguageVersion, "1.8") {
            // Set up disambiguation logic
            disambiguationStrategy {
                // Example disambiguation: Prefer newer versions
                preferNewer()
            }
        }
    }
}
  1. Attribute Definition: Create or reference the attribute you want to apply disambiguation rules to. Here, javaLanguageVersion is used.

  2. Register Disambiguation Rules: Apply the disambiguation strategy using disambiguationStrategy within the attributes block. This example sets up a simple rule to prefer newer versions.

  3. Disambiguation Logic: The preferNewer() method is a placeholder for your custom logic. You can implement more complex rules based on your requirements.

Attribute disambiguation rules have to be registered via the attribute matching strategy that you can obtain from the attributes schema, which is a member of DependencyHandler.