# How to Create Feature Variants for a Library in Gradle

version 8.14.4

Gradle supports the concept of feature variants, allowing a single library to be split into multiple related but distinct modules. Each feature variant can declare its own set of dependencies and can be consumed individually alongside the main library.

For example:

producer/build.gradle.kts

    plugins {
        id("java-library")
    }

    java {
        registerFeature("jsonSupport") {
            usingSourceSet(sourceSets.create("jsonSupport"))
        }
    }

    dependencies {
        "jsonSupportApi"("com.fasterxml.jackson.core:jackson-databind:2.16.0")
    }

consumer/build.gradle.kts

    plugins {
        id("application")
    }

    repositories {
        mavenCentral()
    }

    dependencies {
        implementation("org.example:library:1.0") {
            capabilities {
                requireCapability("org.example:library-json-support")
            }
        }
    }

## Why Use Feature Variants?

Feature variants offer several advantages over traditional dependency management:

- Improved modularity: Clearly defined boundaries between different library functionalities.

- Fine-grained dependency management: Consumers only include the dependencies they specifically require.

- Support for multiple variants: A single library can expose variants tailored to different use cases (e.g., `debug` vs. `release` builds).

Common use-cases include:

- Providing optional dependencies (a robust alternative to Maven optional dependencies).

- Offering multiple mutually-exclusive implementations of runtime features, requiring users to select exactly one variant.

- Supporting optional runtime features that each have unique dependencies.

- Distributing supplementary modules like test fixtures or integration support.

- Enabling additional artifacts that can be optionally included with the main library artifact.

## Step 1: Selection of Features via Capabilities

Dependencies are typically declared using coordinates known as GAV (group, artifact, version), which identify the component. However, a single component can provide multiple variants, each suited for different usages—such as compilation or runtime execution.

Each variant provides a set of capabilities, also identified by GAV coordinates, but best understood as feature descriptions, for example:

- "I provide an SLF4J binding"

- "I provide runtime support for MySQL"

- "I provide a Groovy runtime"

It’s important to note:

- By default, each variant provides a capability that matches the component’s GAV coordinates.

- Two variants providing the same capability cannot coexist in the dependency graph.

- Multiple variants of a single component can coexist if they provide distinct capabilities.

For example, a typical Java library has API and runtime variants, both providing the same capability. Consequently, it’s an error to include both variants simultaneously in a dependency graph. However, it’s permissible to use both runtime and test-fixtures runtime variants simultaneously, as long as these variants declare different capabilities.

To achieve this, consumers must explicitly declare separate dependencies:

- One dependency for the main library (the "main" feature).

- Another dependency explicitly requiring the capability of the additional feature (e.g., test fixtures).

    java {
        registerFeature("testFixtures") {
            usingSourceSet(sourceSets.create("testFixtures"))
            capability("com.example", "library-test-fixtures", version.toString())
        }
    }

| While the resolution engine supports multi-variant components independently of the ecosystem, feature variants are currently only supported by the Java plugins.

## Step 2: Registering features

Features can be declared by applying the `java-library` plugin.

The following code illustrates how to declare a feature named `mongodbSupport`:

build.gradle.kts

    sourceSets {
        create("mongodbSupport") {
            java {
                srcDir("src/mongodb/java")
            }
        }
    }

    java {
        registerFeature("mongodbSupport") {
            usingSourceSet(sourceSets["mongodbSupport"])
        }
    }

build.gradle

    sourceSets {
        mongodbSupport {
            java {
                srcDir 'src/mongodb/java'
            }
        }
    }

    java {
        registerFeature('mongodbSupport') {
            usingSourceSet(sourceSets.mongodbSupport)
        }
    }

Gradle will automatically set up a number of things for you, in a very similar way to how the Java Library Plugin sets up configurations.

When creating feature variants, Gradle automatically configures the following dependency scopes:

- `featureNameApi` for API dependencies of the feature.

- `featureNameImplementation` for implementation-specific dependencies.

- `featureNameRuntimeOnly` for runtime-only dependencies.

- `featureCompileOnly` for compile-only dependencies.

In the example, the feature called "mongodbSupport" automatically creates these configurations:

- `mongodbSupportApi` - used to declare API dependencies for this feature

- `mongodbSupportImplementation` - used to declare implementation dependencies for this feature

- `mongodbSupportRuntimeOnly` - used to declare runtime-only dependencies for this feature

- `mongodbSupportCompileOnly` - used to declare compile-only dependencies for this feature

Additionally, Gradle exposes two variant-specific configurations for external consumption:

- `mongodbSupportRuntimeElements` - used by consumers to fetch the artifacts and API dependencies of this feature

- `mongodbSupportApiElements` - used by consumers to fetch the artifacts and runtime dependencies of this feature

A feature variant should have a corresponding source set named identically.

Gradle automatically creates a `Jar` task for each feature’s source set, using a classifier matching the feature name.

| Do not use the main source set when registering a feature. This behavior will be deprecated in a future version of Gradle.

Most users will only care about the dependency scope configurations, to declare the specific dependencies of this feature:

build.gradle.kts

    dependencies {
        "mongodbSupportImplementation"("org.mongodb:mongodb-driver-sync:3.9.1")
    }

build.gradle

    dependencies {
        mongodbSupportImplementation 'org.mongodb:mongodb-driver-sync:3.9.1'
    }

By convention, Gradle maps a feature variant’s capability using the same group and version as the main component, while the capability’s name is constructed from the main component name followed by a `-` and the kebab-case version of the feature name.

For example, if your component has:

- group: `org.gradle.demo`

- name: `provider`

- version: `1.0`

and you define a feature named `mongodbSupport`, the feature’s capability would be:

- `org.gradle.demo:provider-mongodb-support:1.0`

If you choose a custom capability name or add additional capabilities, it’s recommended to follow this convention.

## Step 3: Publishing features

| Publishing feature variants is only supported using the `maven-publish` and `ivy-publish` plugins.

The Java Library Plugin automatically registers additional variants for you, requiring no extra configuration beyond the standard publication setup:

build.gradle.kts

    plugins {
        `java-library`
        `maven-publish`
    }
    // ...
    publishing {
        publications {
            create("myLibrary", MavenPublication::class.java) {
                from(components["java"])
            }
        }
    }

build.gradle

    plugins {
        id 'java-library'
        id 'maven-publish'
    }
    // ...
    publishing {
        publications {
            myLibrary(MavenPublication) {
                from components.java
            }
        }
    }

Depending on the metadata format used, publishing features may vary:

- Using Gradle Module Metadata, all features are fully preserved, and consumers can fully utilize feature variants.

- Using POM metadata (Maven), features are represented as optional dependencies, and the feature artifacts are published with distinct classifiers.

- Using Ivy metadata, features are published as additional configurations, which are not automatically included by the default configuration.

### Adding Javadoc and Sources JARs

Similar to the main Javadoc and sources JARs, you can configure feature variants to produce their own Javadoc and sources JARs:

build.gradle.kts

    java {
        registerFeature("mongodbSupport") {
            usingSourceSet(sourceSets["mongodbSupport"])
            withJavadocJar()
            withSourcesJar()
        }
    }

build.gradle

    java {
        registerFeature('mongodbSupport') {
            usingSourceSet(sourceSets.mongodbSupport)
            withJavadocJar()
            withSourcesJar()
        }
    }

## Step 4: Dependencies on Features

When consuming feature variants, it’s important to note that feature support can be limited or "lossy" depending on how the features are published. A consumer project can depend on feature variants under these conditions:

- Using a project dependency within a multi-project Gradle build.

- Using Gradle Module Metadata, which must have been explicitly published by the producer.

- Using Ivy metadata, explicitly specifying dependencies on configurations that represent the desired features.

Consumers declare specific feature dependencies by explicitly requiring their capabilities. For example, if a producer defines a feature variant for "MySQL support" as follows:

build.gradle.kts

    group = "org.gradle.demo"

    sourceSets {
        create("mysqlSupport") {
            java {
                srcDir("src/mysql/java")
            }
        }
    }

    java {
        registerFeature("mysqlSupport") {
            usingSourceSet(sourceSets["mysqlSupport"])
        }
    }

    dependencies {
        "mysqlSupportImplementation"("mysql:mysql-connector-java:8.0.14")
    }

build.gradle

    group = 'org.gradle.demo'

    sourceSets {
        mysqlSupport {
            java {
                srcDir 'src/mysql/java'
            }
        }
    }

    java {
        registerFeature('mysqlSupport') {
            usingSourceSet(sourceSets.mysqlSupport)
        }
    }

    dependencies {
        mysqlSupportImplementation 'mysql:mysql-connector-java:8.0.14'
    }

A consumer project can explicitly depend on the "MySQL support" feature by requiring its capability:

build.gradle.kts

    dependencies {
        // This project requires the main producer component
        implementation(project(":producer"))

        // But we also want to use its MySQL support
        runtimeOnly(project(":producer")) {
            capabilities {
                requireCapability("org.gradle.demo:producer-mysql-support")
            }
        }
    }

build.gradle

    dependencies {
        // This project requires the main producer component
        implementation(project(":producer"))

        // But we also want to use its MySQL support
        runtimeOnly(project(":producer")) {
            capabilities {
                requireCapability("org.gradle.demo:producer-mysql-support")
            }
        }
    }

This setup automatically includes the mysql-connector-java dependency on the consumer’s runtime classpath. If multiple dependencies are grouped under the feature variant, all of them are included when the capability is required.

Similarly, when external libraries with feature variants are published using Gradle Module Metadata, consumers can explicitly depend on these external features:

build.gradle.kts

    dependencies {
        // This project requires the main producer component
        implementation("org.gradle.demo:producer:1.0")

        // But we also want to use its MongoDB support
        runtimeOnly("org.gradle.demo:producer:1.0") {
            capabilities {
                requireCapability("org.gradle.demo:producer-mongodb-support")
            }
        }
    }

build.gradle

    dependencies {
        // This project requires the main producer component
        implementation('org.gradle.demo:producer:1.0')

        // But we also want to use its MongoDB support
        runtimeOnly('org.gradle.demo:producer:1.0') {
            capabilities {
                requireCapability("org.gradle.demo:producer-mongodb-support")
            }
        }
    }

## Step 5: Handling Mutually Exclusive Variants

Using capabilities to manage feature variants provides a precise way to handle compatibility between variants.

The key rule to remember is:

> No two variants within a dependency graph may provide the same capability.

This rule allows Gradle to enforce exclusivity between mutually exclusive variants.

For example, suppose you have a library that provides multiple mutually exclusive implementations of a database feature (such as `MySQL`, `PostgreSQL`, and `MongoDB`). By assigning each variant a shared capability, you ensure that these variants cannot coexist in the same dependency graph.

For instance, the producer might define variants as follows:

build.gradle.kts

    java {
        registerFeature("mysqlSupport") {
            usingSourceSet(sourceSets["mysqlSupport"])
            capability("org.gradle.demo", "producer-db-support", "1.0")
            capability("org.gradle.demo", "producer-mysql-support", "1.0")
        }
        registerFeature("postgresSupport") {
            usingSourceSet(sourceSets["postgresSupport"])
            capability("org.gradle.demo", "producer-db-support", "1.0")
            capability("org.gradle.demo", "producer-postgres-support", "1.0")
        }
        registerFeature("mongoSupport") {
            usingSourceSet(sourceSets["mongoSupport"])
            capability("org.gradle.demo", "producer-db-support", "1.0")
            capability("org.gradle.demo", "producer-mongo-support", "1.0")
        }
    }

    dependencies {
        "mysqlSupportImplementation"("mysql:mysql-connector-java:8.0.14")
        "postgresSupportImplementation"("org.postgresql:postgresql:42.2.5")
        "mongoSupportImplementation"("org.mongodb:mongodb-driver-sync:3.9.1")
    }

build.gradle

    java {
        registerFeature('mysqlSupport') {
            usingSourceSet(sourceSets.mysqlSupport)
            capability('org.gradle.demo', 'producer-db-support', '1.0')
            capability('org.gradle.demo', 'producer-mysql-support', '1.0')
        }
        registerFeature('postgresSupport') {
            usingSourceSet(sourceSets.postgresSupport)
            capability('org.gradle.demo', 'producer-db-support', '1.0')
            capability('org.gradle.demo', 'producer-postgres-support', '1.0')
        }
        registerFeature('mongoSupport') {
            usingSourceSet(sourceSets.mongoSupport)
            capability('org.gradle.demo', 'producer-db-support', '1.0')
            capability('org.gradle.demo', 'producer-mongo-support', '1.0')
        }
    }

    dependencies {
        mysqlSupportImplementation 'mysql:mysql-connector-java:8.0.14'
        postgresSupportImplementation 'org.postgresql:postgresql:42.2.5'
        mongoSupportImplementation 'org.mongodb:mongodb-driver-sync:3.9.1'
    }

Here:

- The `mysql-support` variant provides capabilities: `db-support` and `mysql-support`.

- The `postgres-support` variant provides capabilities: `db-support` and `postgres-support`.

- The `mongo-support` variant provides capabilities: `db-support` and `mongo-support`.

If a consumer attempts to include multiple conflicting features, such as both `MySQL` and `PostgreSQL` support:

build.gradle.kts

    dependencies {
        // This project requires the main producer component
        implementation(project(":producer"))

        // Let's try to ask for both MySQL and Postgres support
        runtimeOnly(project(":producer")) {
            capabilities {
                requireCapability("org.gradle.demo:producer-mysql-support")
            }
        }
        runtimeOnly(project(":producer")) {
            capabilities {
                requireCapability("org.gradle.demo:producer-postgres-support")
            }
        }
    }

build.gradle

    dependencies {
        implementation(project(":producer"))

        // Let's try to ask for both MySQL and Postgres support
        runtimeOnly(project(":producer")) {
            capabilities {
                requireCapability("org.gradle.demo:producer-mysql-support")
            }
        }
        runtimeOnly(project(":producer")) {
            capabilities {
                requireCapability("org.gradle.demo:producer-postgres-support")
            }
        }
    }

Dependency resolution will fail with a clear and informative error message:

    Cannot choose between
       org.gradle.demo:producer:1.0 variant mysqlSupportRuntimeElements and
       org.gradle.demo:producer:1.0 variant postgresSupportRuntimeElements
       because they provide the same capability: org.gradle.demo:producer-db-support:1.0