skip to content

CommandLineArgumentProvider

Supplying dynamic JVM or compiler arguments through a nested CommandLineArgumentProvider so they remain tracked inputs. Interviewers ask because raw jvmArgs strings quietly break caching and up-to-date checks.

on this pageshow

questions

5

What is a CommandLineArgumentProvider in Gradle, and why would you use one instead of just adding strings to a task's args?

level: juniorimportance: must knowfreq 45%

answer

  1. SAM interface: asArguments() → Iterable<String>
  2. jvmArgumentProviders / argumentProviders / compilerArgumentProviders
  3. lazy, computed at execution time
  4. annotate underlying values for cache correctness
  5. fixes opaque-string up-to-date bugs

basics

~10 s

It'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 s

A `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 lines
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}")
}

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

for a junior

Know it's an object whose asArguments() returns the args, registered on the task, and that it makes dynamic args play nicely with Gradle.

for a middle

Explain the cache-correctness motivation and name the right collection (jvmArgumentProviders vs argumentProviders).

for a senior

Connect it to input fingerprinting, @Nested scanning, and relocatable cache entries; contrast with opaque string args.

for a principal

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).

context

open as a page

How does Gradle track the inputs of a CommandLineArgumentProvider, and what role does @Nested play?

level: middleimportance: must knowfreq 40%

basics

~10 s

The task's provider collection is annotated @Nested, so Gradle recursively scans each provider's own @Input/@InputFile properties and folds them into the task's input fingerprint. That's how dynamic args participate in up-to-date checks.

open as a page

Why should a CommandLineArgumentProvider read its values through Provider/Property and resolve them inside asArguments() rather than capturing plain values at configuration time?

level: middleimportance: should knowfreq 25%

basics

~10 s

Using Property/Provider keeps the values lazy: they're resolved when asArguments() runs at execution time, so later configuration changes and convention/defaults are picked up. Capturing plain values early freezes stale or unconfigured data.

open as a page

How do you keep CommandLineArgumentProvider arguments that reference file paths from breaking a relocatable build cache?

level: seniorimportance: should knowfreq 28%

basics

~20 s

Track the file via @InputFile/@Classpath with an appropriate @PathSensitivity (often NONE or RELATIVE) so the cache key depends on content, not the absolute path. The absolute path appears only in the returned argument string, which isn't fingerprinted.

open as a page

How do you supply dynamic, cacheable compiler arguments to a JavaCompile task using a CommandLineArgumentProvider, and how does it differ from options.compilerArgs?

level: seniorimportance: nice to knowfreq 18%

basics

~10 s

Add a CommandLineArgumentProvider to JavaCompile's options.compilerArgumentProviders. Like raw options.compilerArgs the strings reach javac, but the provider lets you annotate underlying file/value inputs so up-to-date checks and the build cache stay correct.

open as a page