# Artifact Resolution

version 9.4.0-20260213151510+0000

After constructing a dependency graph, Gradle performs artifact resolution, mapping the resolved graph to a set of artifacts that will be downloaded during the build.

## Artifacts

An artifact is a file that is produced or consumed during the build process. Artifacts are typically files such as compiled libraries, JAR files, AAR files, DLLs, or ZIPs.

Let’s look at the metadata for `org.jetbrains.kotlin:kotlin-stdlib:1.8.10` which showcases several variants and artifacts:

kotlin-stdlib-1.8.10.module

    {
      "variants": [
        {
          "name": "apiElements",
          "attributes": {
            "org.gradle.category": "library",
            "org.gradle.dependency.bundling": "external",
            "org.gradle.jvm.version": "8",
            "org.gradle.libraryelements": "jar",
            "org.gradle.usage": "java-api"
          },
          "files": [
            {
              "name": "kotlin-stdlib-1.8.10-public.jar"
            },
            {
              "name": "kotlin-stdlib-1.8.10-private.jar"
            }
          ]
        },
        {
          "name": "runtimeElements",
          "attributes": {
            "org.gradle.category": "library",
            "org.gradle.dependency.bundling": "external",
            "org.gradle.jvm.version": "8",
            "org.gradle.libraryelements": "jar",
            "org.gradle.usage": "java-runtime"
          },
          "files": [
            {
              "name": "kotlin-stdlib-1.8.10.jar"
            }
          ]
        },
        {
          "name": "jdk7ApiElements",
          "attributes": {
            "org.gradle.usage": "java-api",
            "org.gradle.jvm.version": "7"
          },
          "files": [
            {
              "name": "kotlin-stdlib-jdk7-1.8.10.jar"
            }
          ]
        },
        {
          "name": "jdk8ApiElements",
          "attributes": {
            "org.gradle.usage": "java-api",
            "org.gradle.jvm.version": "8"
          },
          "files": [
            {
              "name": "kotlin-stdlib-jdk8-1.8.10.jar"
            }
          ]
        }
      ]
    }

As we can see there are a number of artifacts available in the metadata:

Variant | Artifact(s) | Purpose
--- | --- | ---
`apiElements` | `kotlin-stdlib-1.8.10-public.jar`, `kotlin-stdlib-1.8.10-private.jar` | Standard Kotlin runtime (default).
`runtimeElements` | `kotlin-stdlib-jdk7-1.8.10.jar` | Provides additional APIs for Java 7 compatibility.
`jdk7ApiElements` | `kotlin-stdlib-jdk8-1.8.10.jar` | Provides additional APIs for Java 8 compatibility.
`jdk8ApiElements` | `kotlin-stdlib-common-1.8.10.jar` | Shared standard library for Kotlin Multiplatform.

Typically, once a variant is selected, its associated artifacts can be automatically resolved. However, there are specific reasons why artifact selection still happens after variant selection.

For example, what if the metadata for `org.jetbrains.kotlin:kotlin-stdlib:1.8.10` looked like this:

kotlin-stdlib-1.8.10.module

    {
      "variants": [
        {
          "name": "runtimeElements",
          "attributes": {
            "org.gradle.category": "library",
            "org.gradle.dependency.bundling": "external",
            "org.gradle.jvm.version": "8",
            "org.gradle.libraryelements": "jar",
            "org.gradle.usage": "java-runtime"
          },
          "files": [
            {
              "name": "kotlin-stdlib-1.8.10.jar"
            }
          ]
        },
        {
          "name": "runtimeElements",
          "attributes": {
            "org.gradle.category": "library",
            "org.gradle.dependency.bundling": "external",
            "org.gradle.jvm.version": "8",
            "org.gradle.libraryelements": "classes",
            "org.gradle.usage": "java-runtime"
          },
          "files": [
            {
              "name": "build/classes/java/main/"
            }
          ]
        },
        {
          "name": "runtimeElements",
          "attributes": {
            "org.gradle.category": "library",
            "org.gradle.dependency.bundling": "external",
            "org.gradle.jvm.version": "8",
            "org.gradle.libraryelements": "resources",
            "org.gradle.usage": "java-runtime"
          },
          "files": [
            {
              "name": "build/resources/main/"
            }
          ]
        }
      ]
    }

How would Gradle know which file to download?

## Artifact Sets

An artifact set is a group of artifacts that belong to a single variant of a module.

A single variant can include multiple artifact sets, each serving a different purpose.

Let’s look at the `org.jetbrains.kotlin:kotlin-stdlib:1.8.10` example:

Variant | Set | Artifacts in the Set
--- | --- | ---
`apiElements` | 1 | jar → The packaged library JAR (`kotlin-stdlib-1.8.10.jar`)
| 2 | tar → The packaged library TAR (`kotlin-stdlib-1.8.10.tar`)
`runtimeElements` | 1 | jar → The packaged library JAR (`kotlin-stdlib-1.8.10.jar`)
| 2 | classes → The compiled .class files (`build/classes/java/main/`)
| 3 | resources → The associated resource files (`build/resources/main/`)
`jdk8ApiElements` | 1 | jar → The packaged library JAR (`kotlin-stdlib-jdk8-1.8.10.jar`)
`jdk7ApiElements` | 1 | jar → The packaged library JAR (`kotlin-stdlib-jdk7-1.8.10.jar`)

The `apiElements` variant of `org.jetbrains.kotlin:kotlin-stdlib:1.8.10` provides two artifact sets—`jar`, and `tar`—each representing the same distributable in a different form. The `runtimeElements` variant of `org.jetbrains.kotlin:kotlin-stdlib:1.8.10` provides three artifact sets—`jar`, `classes`, and `resources`—each representing the same distributable in a different form.

Gradle must now follow a specific process to determine the most appropriate artifact set for the build.

## Artifact Resolution Flow

Artifact selection operates on the dependency graph on a node-by-node basis.

Each node (i.e., variant) in the graph may expose multiple sets of artifacts, but only one of those sets may be selected.

For each node, or variant, in a graph, Gradle performs attribute matching over each set of artifacts exposed by that node to determine the best artifact set.

If no artifact sets match the requested attributes, Gradle will attempt to construct an artifact transform chain to satisfy the request.

For more details on the attribute matching process, see the previous section.

## Available APIs

Gradle APIs can be used to influence the process of artifact selection.

Gradle can then expose the results of artifact selection as an `ArtifactCollection`. More commonly, the results are exposed as a `FileCollection`, which is a flat list of files.

### Implicit Artifact Selection

By default, the attributes used for artifact selection are the same as those used for variant selection during graph resolution. These attributes are specified by the `Configuration.getAttributes()` property.

To perform artifact selection (and implicitly, graph resolution) using these default attributes, use the `FileCollection` and `ArtifactCollection` APIs.

| Files can also be accessed from the configuration’s `ResolvedConfiguration`, `LenientConfiguration`, `ResolvedArtifact` and `ResolvedDependency` APIs. However, these APIs are in maintenance mode and are discouraged for use in new development. These APIs perform artifact selection using the default attributes.

#### Resolving files

To resolve files, we first define a task that accepts a `ConfigurableFileCollection` as input:

build.gradle.kts

    abstract class ResolveFiles : DefaultTask() {

        @get:InputFiles
        abstract val files: ConfigurableFileCollection

        @TaskAction
        fun print() {
            files.forEach {
                println(it.name)
            }
        }
    }

build.gradle

    abstract class ResolveFiles extends DefaultTask {

        @InputFiles
        abstract ConfigurableFileCollection getFiles()

        @TaskAction
        void print() {
            files.each {
                println(it.name)
            }
        }
    }

Then, we can wire up a resolvable configuration’s files to the task’s input. The `Configuration` directly implements `FileCollection` and can be wired directly:

build.gradle.kts

    tasks.register<ResolveFiles>("resolveConfiguration") {
        files.from(configurations.runtimeClasspath)
    }

build.gradle

    tasks.register("resolveConfiguration", ResolveFiles) {
        files.from(configurations.runtimeClasspath)
    }

Running the `resolveConfiguration` task produces:

    > Task :resolveConfiguration
    junit-platform-commons-1.11.0.jar
    junit-jupiter-api-5.11.0.jar
    opentest4j-1.3.0.jar

#### Resolving artifacts

Instead of consuming the files directly from the implicit artifact selection process, we can consume the artifacts, which contain both the files and the metadata.

This process is slightly more complicated, as in order to maintain Configuration Cache compatibility, we need to split the fields of `ResolvedArtifactResult` into two task inputs:

build.gradle.kts

    data class ArtifactDetails(
        val id: ComponentArtifactIdentifier,
        val variant: ResolvedVariantResult
    )

    abstract class ResolveArtifacts : DefaultTask() {

        @get:Input
        abstract val details: ListProperty<ArtifactDetails>

        @get:InputFiles
        abstract val files: ListProperty<File>

        fun from(artifacts: Provider<Set<ResolvedArtifactResult>>) {
            details.set(artifacts.map {
                it.map { artifact -> ArtifactDetails(artifact.id, artifact.variant) }
            })
            files.set(artifacts.map {
                it.map { artifact -> artifact.file }
            })
        }

        @TaskAction
        fun print() {
            assert(details.get().size == files.get().size)
            details.get().zip(files.get()).forEach { (details, file) ->
                println("${details.variant.displayName}:${file.name}")
            }
        }
    }

build.gradle

    class ArtifactDetails {
        ComponentArtifactIdentifier id
        ResolvedVariantResult variant

        ArtifactDetails(ComponentArtifactIdentifier id, ResolvedVariantResult variant) {
            this.id = id
            this.variant = variant
        }
    }

    abstract class ResolveArtifacts extends DefaultTask {

        @Input
        abstract ListProperty<ArtifactDetails> getDetails()

        @InputFiles
        abstract ListProperty<File> getFiles()

        void from(Provider<Set<ResolvedArtifactResult>> artifacts) {
            details.set(artifacts.map {
                it.collect { artifact -> new ArtifactDetails(artifact.id, artifact.variant) }
            })
            files.set(artifacts.map {
                it.collect { artifact -> artifact.file }
            })
        }

        @TaskAction
        void print() {
            List<ArtifactDetails> allDetails = details.get()
            List<File> allFiles = files.get()

            assert allDetails.size() == allFiles.size()
            for (int i = 0; i < allDetails.size(); i++) {
                def details = allDetails.get(i)
                def file = allFiles.get(i)
                println("${details.variant.displayName}:${file.name}")
            }
        }
    }

This task is initialized similarly to the file resolution task:

build.gradle.kts

    tasks.register<ResolveArtifacts>("resolveIncomingArtifacts") {
        from(configurations.runtimeClasspath.flatMap { it.incoming.artifacts.resolvedArtifacts })
    }

build.gradle

    tasks.register("resolveIncomingArtifacts", ResolveArtifacts) {
        from(configurations.runtimeClasspath.incoming.artifacts.resolvedArtifacts)
    }

Running this task, we can see that file metadata is included in the output:

    org.junit.platform:junit-platform-commons:1.11.0 variant runtimeElements:junit-platform-commons-1.11.0.jar
    org.junit.jupiter:junit-jupiter-api:5.11.0 variant runtimeElements:junit-jupiter-api-5.11.0.jar
    org.opentest4j:opentest4j:1.3.0 variant runtimeElements:opentest4j-1.3.0.jar

### Custom Artifact Selection

The `ArtifactView` API operates on top of the resolved graph, defined by a ../javadoc/org/gradle/api/artifacts/result/ResolutionResult.html[`ResolutionResult`].

The API provides flexible ways to access the resolved artifacts:

- `FileCollection` - A flat list of files, which is the most commonly way to work with resolved artifacts.

- `ArtifactCollection` - Offers access to both the metadata and the files of resolved artifacts, allowing for more advanced artifact handling.

When you call a configuration’s `getFiles()`, Gradle selects artifacts based on the attributes used during graph resolution. However, the `ArtifactView` API is more flexible. It allows you to resolve artifacts from the graph with custom attributes.

### Artifact views

An `ArtifactView` allows you to:

1. Query artifacts with different attributes:

   - Suppose the graph resolved a dependency’s `runtime` variant. You can use an `ArtifactView` to extract artifacts from its `api` variant instead, even if they weren’t originally part of the resolved graph.

2. Extract specific types of artifacts:

   - You can request only the `.jar` files or a specific artifact type (e.g., sources, Javadoc) by specifying an attribute like `artifactType`.

3. Avoid side effects:

   - Using an `ArtifactView` allows you to extract artifacts without changing the underlying dependency resolution logic or configuration state.

In the following example, a producer project creates a library with typical Java library variants (`runtimeElements`, `apiElements`). We also create a custom variant called `apiProductionElements` with the artifact `production.jar` and attribute `org.gradle.category:production`:

producer/build.gradle.kts

    plugins {
        `java-library`
    }

    repositories {
        mavenCentral()
    }

    dependencies {
        // Define some dependencies here
    }

    // Define a task that produces a custom artifact
    tasks.register<Jar>("createProductionArtifact") {
        archiveBaseName.set("production")
        from(sourceSets["main"].output)
        destinationDirectory.set(file("build/libs"))
    }

    configurations {
        // Define a custom configuration and extend from apiElements
        create("apiProductionElements") {
            extendsFrom(configurations.apiElements.get())
            outgoing.artifacts.clear()
            attributes {
                attribute(Category.CATEGORY_ATTRIBUTE, objects.named("production"))
            }
            artifacts {
                add("apiProductionElements", tasks.named("createProductionArtifact"))
            }
        }
    }

producer/build.gradle

    plugins {
        id 'java-library'
    }

    repositories {
        mavenCentral()
    }

    dependencies {
        // Define some dependencies here
    }

    // Define a task that produces a custom artifact
    tasks.register('createProductionArtifact', Jar) {
        archiveBaseName.set('production')
        from(sourceSets.main.output)
        destinationDirectory.set(file('build/libs'))
    }

    configurations {
        // Define a custom configuration and extend from apiElements
        apiProductionElements {
            extendsFrom(configurations.apiElements)
            attributes {
                attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, 'production'))
            }
            artifacts {
                add('apiProductionElements', tasks.named('createProductionArtifact'))
            }
        }
    }

We can view the available variants of this library as well as their corresponding artifacts and attributes with a custom task called `checkProducerAttributes`. Here is an abridged output showing the relevant variants of this library, along with their corresponding artifacts and attributes:

    Configuration: apiElements
    Attributes:
      - org.gradle.category -> library
      - org.gradle.usage -> java-api
      - org.gradle.libraryelements -> jar
      - org.gradle.dependency.bundling -> external
    Artifacts:
      - producer.jar

    Configuration: apiProductionElements
    Attributes:
      - org.gradle.category -> production
    Artifacts:
      - production.jar

    Configuration: mainSourceElements
    Attributes:
      - org.gradle.dependency.bundling -> external
      - org.gradle.category -> verification
      - org.gradle.verificationtype -> main-sources
    Artifacts:
      - /src/main/resources
      - /src/main/java

    Configuration: runtimeElements
    Attributes:
      - org.gradle.category -> library
      - org.gradle.usage -> java-runtime
      - org.gradle.libraryelements -> jar
      - org.gradle.dependency.bundling -> external
    Artifacts:
      - producer.jar

A Java application is a consumer of this Java library:

consumer/build.gradle.kts

    plugins {
        application
    }

    repositories {
        mavenCentral()
    }

    // Declare the dependency on the producer project
    dependencies {
        implementation(project(":producer")) // This references another subproject in the same build
    }

consumer/build.gradle

    plugins {
        id 'application'
    }

    repositories {
        mavenCentral()
    }

    // Declare the dependency on the producer project
    dependencies {
        implementation project(':producer')
    }

By default, the application, as the consumer, will consume the expected variant when it is run. We can verify this with another custom task called `checkResolvedVariant` that prints out the following:

    RuntimeClasspath Configuration:
    - Component: project :producer
        - Variant: runtimeElements
           - org.gradle.category -> library
           - org.gradle.usage -> java-runtime
           - org.gradle.libraryelements -> jar
           - org.gradle.dependency.bundling -> external
           - org.gradle.jvm.version -> 11
    - Artifact: producer.jar

As expected, for the `runtimeClasspath`, the application consumes the `runtimeElements` variant of the library which is available as the artifact `producer.jar`. It uses the attributes `org.gradle.category:library` and `org.gradle.usage:java-runtime` to select this variant.

Now, let’s create an `ArtifactView` to select one of the other artifacts provided by the library. We do this by using an `ArtifactView` with the attribute `org.gradle.category:classes` so that instead of the jar file, we get the sources:

consumer/build.gradle.kts

    tasks.register("artifactWithAttributeAndView") {
        val configuration = configurations.runtimeClasspath
        println("ArtifactView with attribute 'libraryelements = classes' for ${configuration.name}:")
        val artifactView = configuration.get().incoming.artifactView {
            attributes {
                attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements::class, "classes"))
            }
        }
        println("- Attributes:")
        artifactView.attributes.keySet().forEach { attribute ->
            val value = artifactView.attributes.getAttribute(attribute)
            println("  - ${attribute.name} = ${value}")
        }
        artifactView.artifacts.artifactFiles.files.forEach { file ->
            println("- Artifact: ${file.name}")
        }
    }

    tasks.register("artifactWithAttributeAndVariantReselectionView") {
        val configuration = configurations.runtimeClasspath
        println("ArtifactView with attribute 'category = production' for ${configuration.name}:")
        val artifactView = configuration.get().incoming.artifactView {
            withVariantReselection()
            attributes {
                attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category::class, "production"))
            }
        }
        println("- Attributes:")
        artifactView.attributes.keySet().forEach { attribute ->
            val value = artifactView.attributes.getAttribute(attribute)
            println("  - ${attribute.name} = ${value}")
        }
        artifactView.artifacts.artifactFiles.files.forEach { file ->
            println("- Artifact: ${file.name}")
        }
    }

consumer/build.gradle

    tasks.register('artifactWithAttributeAndView') {
        def configuration = configurations.runtimeClasspath
        println "ArtifactView with attribute 'libraryelements = classes' for ${configuration.name}:"
        def artifactView = configuration.incoming.artifactView {
            attributes {
                attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements, 'classes'))
            }
        }
        println "- Attributes:"
        artifactView.attributes.keySet().each { attribute ->
            def value = artifactView.attributes.getAttribute(attribute)
            println "  - ${attribute.name} = ${value}"
        }
        artifactView.artifacts.artifactFiles.files.each { file ->
            println "- Artifact: ${file.name}"
        }
    }

    tasks.register('artifactWithAttributeAndVariantReselectionView') {
        def configuration = configurations.runtimeClasspath
        println "ArtifactView with attribute 'category = production' for ${configuration.name}:"
        def artifactView = configuration.incoming.artifactView {
            withVariantReselection()
            attributes {
                attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category,'production'))
            }
        }
        println "- Attributes:"
        artifactView.attributes.keySet().each { attribute ->
            def value = artifactView.attributes.getAttribute(attribute)
            println "  - ${attribute.name} = ${value}"
        }
        artifactView.artifacts.artifactFiles.files.each { file ->
            println "- Artifact: ${file.name}"
        }
    }

We run the task `artifactWithAttributeAndView` to see that we get the main artifact instead:

    ArtifactView with attribute 'libraryelements = classes' for runtimeClasspath:
    - Attributes:
      - org.gradle.libraryelements = classes
      - org.gradle.category = library
      - org.gradle.usage = java-runtime
      - org.gradle.dependency.bundling = external
      - org.gradle.jvm.environment = standard-jvm
    - Artifact: main

Now, let’s create an `ArtifactView` to select our custom variant `apiProductionElements` by specifying the attribute `org.gradle.category:production` and forcing Gradle to reselect a new variant:

consumer/build.gradle.kts

    tasks.register("artifactWithAttributeAndVariantReselectionView") {
        val configuration = configurations.runtimeClasspath
        println("ArtifactView with attribute 'category = production' for ${configuration.name}:")
        val artifactView = configuration.get().incoming.artifactView {
            withVariantReselection()
            attributes {
                attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category::class, "production"))
            }
        }
        println("- Attributes:")
        artifactView.attributes.keySet().forEach { attribute ->
            val value = artifactView.attributes.getAttribute(attribute)
            println("  - ${attribute.name} = ${value}")
        }
        artifactView.artifacts.artifactFiles.files.forEach { file ->
            println("- Artifact: ${file.name}")
        }
    }

consumer/build.gradle

    tasks.register('artifactWithAttributeAndVariantReselectionView') {
        def configuration = configurations.runtimeClasspath
        println "ArtifactView with attribute 'category = production' for ${configuration.name}:"
        def artifactView = configuration.incoming.artifactView {
            withVariantReselection()
            attributes {
                attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category,'production'))
            }
        }
        println "- Attributes:"
        artifactView.attributes.keySet().each { attribute ->
            def value = artifactView.attributes.getAttribute(attribute)
            println "  - ${attribute.name} = ${value}"
        }
        artifactView.artifacts.artifactFiles.files.each { file ->
            println "- Artifact: ${file.name}"
        }
    }

As expected, the `apiProductionElements` variant is selected along with the `production.jar` artifact:

    ArtifactView with attribute 'libraryelements = classes' for runtimeClasspath:
    - Attributes:
      - org.gradle.libraryelements = classes
      - org.gradle.category = library
      - org.gradle.usage = java-runtime
      - org.gradle.dependency.bundling = external
      - org.gradle.jvm.environment = standard-jvm
    - Artifact: main