> ## Documentation Index
> Fetch the complete documentation index at: https://docs.root.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Gradle

> Root.io Gradle Plugin automatically patches vulnerable Java dependencies at build time.

The Root.io Gradle Plugin intercepts dependency resolution and transparently substitutes vulnerable dependencies with Root-patched versions from `pkg.root.io/maven/`. You declare the versions you know, and the plugin handles the rest—no changes to your dependency declarations required.

<Card title="Root.io Gradle Plugin" icon="github" href="https://github.com/rootio-avr/rootio_gradle_plugin">
  View the source code, report issues, or contribute to the Root.io Gradle Plugin.
</Card>

## Prerequisites

* Gradle 7.0+
* JDK 11+
* Root.io API key ([setup instructions](/getting-started/authentication))

## Installation

### Option 1: Gradle Plugin Portal (Recommended)

The plugin is published to the [Gradle Plugin Portal](https://plugins.gradle.org/plugin/io.root.patcher).

Apply the plugin in `build.gradle.kts`:

```kotlin theme={null}
plugins {
    id("io.root.patcher") version "0.2.0"
}
```

If your `pluginManagement` block explicitly lists repositories, make sure `gradlePluginPortal()` is included:

```kotlin theme={null}
pluginManagement {
    repositories {
        gradlePluginPortal()
    }
}
```

### Option 2: Root.io Maven Repository

Add the Root.io Gradle Plugin repository to `settings.gradle.kts`:

```kotlin theme={null}
pluginManagement {
    repositories {
        maven {
            url = uri("https://pkg.root.io/gradle-plugins")
            credentials {
                username = "token"
                password = providers.environmentVariable("ROOTIO_API_KEY").get()
            }
        }
        gradlePluginPortal()
    }
}
```

Apply the plugin in `build.gradle.kts`:

```kotlin theme={null}
plugins {
    id("io.root.patcher") version "0.2.0"
}
```

### Option 3: Local Maven Repository

For testing or air-gapped environments:

```bash theme={null}
cd rootio_gradle_plugin/
make publish-local
```

Then add `mavenLocal()` to your `pluginManagement` repositories:

```kotlin theme={null}
pluginManagement {
    repositories {
        mavenLocal()
        gradlePluginPortal()
    }
}
```

### Option 4: Private JFrog Artifactory

Publish and consume the plugin through your own JFrog Artifactory instance - useful when you need full control over the registry or want to keep builds air-gapped from the public internet.

#### Publishing

Set the following environment variables and run `make publish`:

```bash theme={null}
export ARTIFACTORY_URL=https://your-instance.jfrog.io/artifactory/your-repo
export ARTIFACTORY_USER=your-username
export ARTIFACTORY_PASSWORD=your-api-key
make publish
```

This publishes both the plugin JAR and the Gradle plugin marker artifact (`io.root.patcher:io.root.patcher.gradle.plugin`) that Gradle requires for `plugins {}` block resolution.

#### Consuming

Add the Artifactory repository to `pluginManagement` in your `settings.gradle.kts`:

```kotlin theme={null}
pluginManagement {
    repositories {
        maven {
            url = uri("https://your-instance.jfrog.io/artifactory/your-repo")
            credentials {
                username = providers.environmentVariable("JFROG_USERNAME").orNull
                password = providers.environmentVariable("JFROG_TOKEN").orNull
            }
        }
    }
}
```

Then apply the plugin as usual in `build.gradle.kts`:

```kotlin theme={null}
plugins {
    id("io.root.patcher") version "0.2.0"
}
```

#### Using JFrog as a Proxy for pkg.root.io

If your organization routes all artifact traffic through an internal proxy, you can also point the plugin at your JFrog instance for resolving patched artifacts - instead of reaching `pkg.root.io` directly.

In JFrog, create a remote Maven repository with the remote URL set to `https://pkg.root.io/maven` and configure your Root.io API key as the upstream password. JFrog will handle authentication to `pkg.root.io` on behalf of your builds - individual developers never need a Root.io API key.

In your `build.gradle.kts`, override `pkgUrl` and supply JFrog credentials via `pkgUsername`/`pkgPassword`:

```kotlin theme={null}
rootio {
    pkgUrl.set("https://your-instance.jfrog.io/artifactory/your-remote-repo")
    pkgUsername.set(providers.environmentVariable("JFROG_USERNAME").get())
    pkgPassword.set(providers.environmentVariable("JFROG_TOKEN").get())
}
```

<Note>
  `pkgUsername`/`pkgPassword` control only where patched artifacts are downloaded from, not where the plugin itself is resolved.
</Note>

## How It Works

When you run a build, the plugin will:

1. Query Root.io API for patches on your declared dependencies
2. Substitute patched versions if available
3. Register `pkg.root.io/maven/` as a Maven repository
4. Cache results locally at `.gradle/rootio-cache/`

## Configuration

Configure the plugin in `build.gradle.kts`:

```kotlin theme={null}
rootio {
    apiKey.set("your-api-key-here")  // Optional if using env vars
    apiUrl.set("https://api.root.io")  // Default
    pkgUrl.set("https://pkg.root.io/maven")  // Default; override to use a proxy
    pkgUsername.set("...")  // Optional; overrides apiKey for pkg repo auth
    pkgPassword.set("...")  // Optional; overrides apiKey for pkg repo auth
    ttlHours.set(24)  // Cache TTL (0 = no cache)
    maxRetries.set(3)  // Max retry attempts
    retryBaseDelayMs.set(1000)  // Exponential backoff base
    allowInsecurePkgRepo.set(false)  // Allow HTTP (test only)
    ignore = ["com.example:my-lib@1.0.0-root.io.5"]  // Skip specific patch versions
}
```

### Configuration Properties

| Property               | Default                     | Description                                                                                                                                                                                           |
| ---------------------- | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `apiKey`               | -                           | Root.io API key (see [API Key Resolution](#api-key-resolution)). Also used as a fallback password for the default `pkg.root.io` Maven repository when `pkgUsername`/`pkgPassword` are not set.        |
| `apiUrl`               | `https://api.root.io`       | Root.io API base URL                                                                                                                                                                                  |
| `pkgUrl`               | `https://pkg.root.io/maven` | Maven repository URL from which patched artifacts are resolved. Override this when routing through a proxy (see [Option 4](#option-4-private-jfrog-artifactory)).                                     |
| `pkgUsername`          | -                           | Username for the pkg Maven repository. When set together with `pkgPassword`, takes precedence over `apiKey` for repository authentication.                                                            |
| `pkgPassword`          | -                           | Password for the pkg Maven repository. When set together with `pkgUsername`, takes precedence over `apiKey` for repository authentication.                                                            |
| `ttlHours`             | `24`                        | Cache TTL in hours (0 disables caching)                                                                                                                                                               |
| `maxRetries`           | `3`                         | Max retry attempts for transient API failures                                                                                                                                                         |
| `retryBaseDelayMs`     | `1000`                      | Base delay (ms) for exponential backoff                                                                                                                                                               |
| `allowInsecurePkgRepo` | `false`                     | Allow plain HTTP for package repo (test/local only)                                                                                                                                                   |
| `ignore`               | `[]`                        | Patch versions to skip, as `group:artifact@patch-version`. The list is sent to the API so the server returns the best available alternative. See [Ignoring patch versions](#ignoring-patch-versions). |

## API Key Resolution

The plugin resolves the API key in this order (highest priority first):

1. **Build script**: `rootio { apiKey.set("...") }`
2. **Environment variable**: `export ROOTIO_API_KEY=...`
3. **JVM system property**: `systemProp.ROOTIO_API_KEY=...` in `~/.gradle/gradle.properties`
4. **`.env` file**: `ROOTIO_API_KEY=...` in project root

<Warning>
  Keep your API key out of source control. Use environment variables or JVM system properties for CI/CD.
</Warning>

## Multi-Project Setup

Apply the plugin once at the root and propagate to all subprojects:

```kotlin theme={null}
// Root build.gradle.kts
plugins {
    id("io.root.patcher") version "0.2.0" apply false
}

subprojects {
    apply(plugin = "io.root.patcher")

    repositories {
        mavenCentral()
    }
}

// app/build.gradle.kts
dependencies {
    implementation("io.netty:netty-all:4.1.42.Final")
}

// lib/build.gradle.kts
dependencies {
    implementation("commons-beanutils:commons-beanutils:1.9.3")
}
```

Run the build:

```bash theme={null}
export ROOTIO_API_KEY="your-api-key-here"
./gradlew build
```

All subprojects will automatically use Root-patched dependencies.

## Ignoring patch versions

If a specific Root.io patch version causes a regression, you can exclude it. The ignore list is sent to the Root.io API with every request — the server skips the excluded version and returns the best available alternative.

**Example:** `commons-lang3` was patched to `3.12.0-root.io.5` but it broke your build. Ignore it to fall back to `3.12.0-root.io.4`:

```
# .rootioignore
org.apache.commons:commons-lang3@3.12.0-root.io.5
```

The API will respond with `3.12.0-root.io.4` instead.

### Three ways to configure ignores (all merged together)

**1. `.rootioignore` file** — place in the project root directory, one entry per line:

```
# Comments and blank lines are ignored
org.apache.commons:commons-lang3@3.12.0-root.io.5
com.google.guava:guava@31.1-jre-root.io.2
```

**2. Build script** — `rootio { ignore = [...] }`:

```kotlin theme={null}
rootio {
    ignore = ["org.apache.commons:commons-lang3@3.12.0-root.io.5"]
}
```

**3. Gradle property** — `-Prootio.ignore=...` (comma-separated), useful for one-off builds:

```bash theme={null}
./gradlew build -Prootio.ignore=org.apache.commons:commons-lang3@3.12.0-root.io.5
```

The entry format is `group:artifact@patch-version` (exact match, case-sensitive).

<Note>
  Changing the ignore list for a dependency automatically invalidates any cached result for that dependency, so the API is re-queried with the updated list.
</Note>

## Caching

The plugin caches Root.io API responses locally at `.gradle/rootio-cache/` to avoid repeated network calls.

The cache is **not used** when:

* The cached response is older than the TTL (default: 24 hours)
* `ttlHours.set(0)` is configured (caching disabled)
* A dependency is queried for the first time
* The cache directory has been manually cleared

To clear the cache:

```bash theme={null}
rm -rf .gradle/rootio-cache/
```

### Detailed Flow

1. **Plugin applies** to your project and creates the `rootio {}` configuration extension
2. **API key resolution** happens in priority order (build script → env var → system property → `.env`)
3. **Root.io Maven repository** is registered automatically with credentials
4. **Resolution hooks** are installed on every resolvable configuration
5. **For each dependency**, the plugin:
   * Checks the local cache (`.gradle/rootio-cache/`) for a valid entry (keyed on coords + ignore list)
   * If cache miss, queries Root.io API (`/v3/analyze/maven`) with the dependency and the ignore list
   * Substitutes the dependency coordinates if a patch is available
   * Logs the patching decision
6. **Gradle resolves** the patched version from `pkg.root.io/maven/`

## Troubleshooting

| Issue                                         | Solution                                                                                                             |
| --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `401 Unauthorized` from Root.io API           | Verify `ROOTIO_API_KEY` is set correctly (see [API Key Resolution](#api-key-resolution))                             |
| `404 Not Found` for patched artifact          | Confirm the patched version exists in `pkg.root.io/maven/` (check Root.io dashboard)                                 |
| Plugin not applying patches                   | Check Gradle console output for `[Root.io]` log messages. Ensure plugin is applied before dependencies are declared. |
| Network timeouts                              | Increase `retryBaseDelayMs` and `maxRetries` in the `rootio {}` block                                                |
| Cache is stale                                | Delete `.gradle/rootio-cache/` or set `ttlHours.set(0)` to disable caching                                           |
| `ResolutionStrategy` conflicts                | Root.io plugin uses `eachDependency` hooks. Ensure no other plugins conflict with dependency resolution.             |
| Multi-project builds not patching subprojects | Verify `apply(plugin = "io.root.patcher")` is in the `subprojects {}` block                                          |

<Note>
  If you encounter issues, check the Gradle build output for `[Root.io]` prefixed log messages. Run with `--info` or `--debug` for detailed output: `./gradlew build --info`
</Note>
