What is a CommandLineArgumentProvider in Gradle, and why would you use one instead of just adding strings to a task's args?
answer
- SAM interface: asArguments() → Iterable<String>
- jvmArgumentProviders / argumentProviders / compilerArgumentProviders
- lazy, computed at execution time
- annotate underlying values for cache correctness
- fixes opaque-string up-to-date bugs
basics
~10 sIt's a small object that supplies command-line arguments lazily via asArguments(). You register it on a task (e.g. JavaExec/Test) so the actual arguments are computed at execution time rather than hardcoded as plain strings.
solid answer
~40 sA `CommandLineArgumentProvider` is a functional interface with a single method, `Iterable<String> asArguments()`, that supplies arguments to a process-launching task such as `JavaExec`, `Test`, or `JavaCompile`. You add it to a collection like `jvmArgumentProviders` or `argumentProviders` instead of putting raw strings in `args`/`jvmArgs`. The key reason is **build correctness and caching**. Plain strings added to `args` are eager and opaque: if an argument references a file path, Gradle can't see that it's an input file, so up-to-date checks and the build cache may give wrong results. A provider lets you annotate the *underlying* values (with `@Input`, `@InputFile`, `@Classpath`, etc.) so Gradle tracks them properly, while still computing the final argument strings lazily. It keeps dynamic arguments both flexible and cacheable.
code
kotlin · 14 linesabstract class ConfigArgs : CommandLineArgumentProvider {
@get:InputFile
@get:PathSensitive(PathSensitivity.NONE)
abstract val configFile: RegularFileProperty
override fun asArguments(): Iterable<String> =
listOf("-Dapp.config=${configFile.get().asFile.absolutePath}")
}
tasks.named<Test>("test") {
val args = objects.newInstance(ConfigArgs::class.java)
args.configFile.set(layout.projectDirectory.file("config/app.conf"))
jvmArgumentProviders.add(args)
}go deeper
Know it's an object whose asArguments() returns the args, registered on the task, and that it makes dynamic args play nicely with Gradle.
Explain the cache-correctness motivation and name the right collection (jvmArgumentProviders vs argumentProviders).
Connect it to input fingerprinting, @Nested scanning, and relocatable cache entries; contrast with opaque string args.
Frame it as part of a build-correctness policy: ban absolute paths in raw args across the codebase, standardize providers for agents/config so caching and remote-cache hit rates stay high.
## What it is `org.gradle.process.CommandLineArgumentProvider` is a tiny SAM (single-abstract-method) interface: ```java public interface CommandLineArgumentProvider { Iterable<String> asArguments(); } ``` Process-launching tasks expose collections of these providers: - `JavaExec` / `Test` → `jvmArgumentProviders` (for JVM args like `-D`, `-javaagent`) and, for application args, `argumentProviders`. - `JavaCompile` → `options.compilerArgumentProviders`. Gradle calls `asArguments()` **at task execution time** and appends the returned strings to the final command line. ## Why not just use `args`/`jvmArgs`? When you write `test { jvmArgs '-Dconfig=/some/path/app.conf' }`, that string is opaque to Gradle. Two problems: 1. **Up-to-date / cache correctness.** If the argument names a file that the process reads, Gradle has no idea it's an input. A change to that file won't invalidate the task, or worse, a build-cache hit will reuse a stale result. 2. **Path portability.** Absolute paths baked into argument strings break the relocatable build cache (a cache entry built on machine A won't match machine B). A provider solves both because you can put **annotated, tracked properties** on the provider class and derive the strings inside `asArguments()`. ## The pattern ```kotlin abstract class ConfigArgs : CommandLineArgumentProvider { @get:InputFile @get:PathSensitive(PathSensitivity.NONE) abstract val configFile: RegularFileProperty override fun asArguments(): Iterable<String> = listOf("-Dapp.config=${configFile.get().asFile.absolutePath}") } ``` Gradle scans the provider's annotated properties (because the provider is registered via `@Nested` on the host task's collection) and folds them into the task's input fingerprint. The `absolutePath` string itself is *not* an input — the **content/normalized path** of `configFile` is, because of `@InputFile` + `@PathSensitive`. ## When to reach for it Use a provider whenever a dynamic argument depends on a file, a directory, a computed value, or anything that should participate in up-to-date checks and caching. Use plain `args`/`jvmArgs` only for truly static literals (e.g. `-XX:+UseParallelGC`).
- Which collection do you add the provider to for JVM args versus application args on a Test/JavaExec task?`jvmArgumentProviders` for JVM-level args (the ones that would go in `jvmArgs`), and `argumentProviders` for the program's own application arguments (the ones that would go in `args`).
- Does the provider run during configuration or execution?Execution. `asArguments()` is called when the task actually runs, which is what makes the arguments lazy and lets them reflect the final resolved property values.
Plain args is like writing a final shopping receipt by hand — Gradle can't tell which line items are perishable. A provider is the itemized cart with labels, so Gradle knows exactly which inputs to watch.
saying these in an interview costs you the question
- Saying it's just a fancy way to set strings — missing the input-tracking/caching motivation.
- Claiming `asArguments()` runs at configuration time.
- Thinking you still also need to add the same args to `jvmArgs` (you don't — the provider replaces them).