skip to content

You're authoring a custom task that takes a set of jars. How do you decide between @Classpath, @CompileClasspath, and @InputFiles with @PathSensitive for that property?

level: seniorimportance: should knowfreq 30%

answer

  1. is it an ordered classpath?
  2. API only → @CompileClasspath
  3. runtime bytes → @Classpath
  4. file set → @InputFiles + @PathSensitive
  5. weakest correct path sensitivity; avoid ABSOLUTE

basics

~20 s

Use @CompileClasspath if you only need the jars' public API, @Classpath if you need real runtime bytes but order matters and names/paths don't, and @InputFiles with an explicit @PathSensitive (RELATIVE/NAME_ONLY/NONE) for non-classpath file inputs where order is irrelevant.

solid answer

~50 s

The decision hinges on **what semantics the jars carry** for your task: - **@CompileClasspath** — you compile/analyze against the *API* only. Most relocatable and cache-friendly; impl-only upstream changes won't invalidate you. Order respected, names/paths and method bodies ignored. - **@Classpath** — you need the *actual runtime contents* (packaging a fat jar, running, classloading). Order is significant; names/paths ignored; full jar-entry content hashed. - **@InputFiles + @PathSensitive(RELATIVE | NAME_ONLY | NONE)** — the files are *not* an ordered classpath; they're just a file set. Choose the weakest path sensitivity that's still correct: `NONE` (content only), `NAME_ONLY`, or `RELATIVE` (relative layout matters). Avoid the `ABSOLUTE` default — it breaks relocatability. Rule of thumb: reach for the **most permissive** annotation that's still correct, because more normalization → more cache hits. Classpath annotations also let you apply settings-level runtime-classpath normalization on top.

code

kotlin · 14 lines
kotlin
abstract class AnalyzeApis : DefaultTask() {
    @get:CompileClasspath
    abstract val apiJars: ConfigurableFileCollection

    @get:Classpath
    abstract val runtimeJars: ConfigurableFileCollection

    @get:InputFiles
    @get:PathSensitive(PathSensitivity.RELATIVE)
    abstract val configFiles: ConfigurableFileCollection

    @get:OutputDirectory
    abstract val report: DirectoryProperty
}

go deeper

for a junior

Pick @Classpath for jars consumed as a classpath; know @InputFiles exists for plain files.

for a middle

Distinguish @CompileClasspath vs @Classpath by API-vs-runtime need and set explicit @PathSensitive.

for a senior

Apply the 'most permissive correct' principle and justify the relocatability/correctness trade-off.

for a principal

Define authoring guidelines so teams' custom tasks are cacheable and relocatable by default.

## The core question: is it a classpath? A *classpath* has two semantic properties — **order matters**, **names/paths don't**. If your jars behave that way (they're consumed as a JVM/compiler classpath), use a classpath annotation. If order is irrelevant and they're just an arbitrary file set, use `@InputFiles` and pick a path sensitivity. ## The choices ### @CompileClasspath Fingerprints only the **ABI** (public signatures, inlined constants, visible annotations). Best for tasks that merely compile or do API-level analysis against the jars. Maximum normalization → fewest invalidations. ### @Classpath Fingerprints the **full logical contents** of each jar entry, preserves order, ignores names/paths. Use when the runtime bytes genuinely matter: assembling distributions, running the code, generating something that depends on implementation, not just signatures. ### @InputFiles with @PathSensitive For a non-ordered file collection. `@PathSensitive` controls how much of the path is part of the fingerprint: - `NONE` — only file **content** matters; paths fully ignored. - `NAME_ONLY` — file name + content. - `RELATIVE` — path relative to the input root + content (good for directory trees where layout is meaningful). - `ABSOLUTE` — the default; full absolute path. **Avoid** for cacheable/relocatable tasks. ## Decision flow ``` Is it consumed as an ordered classpath? ├── yes │ ├── need only the API? -> @CompileClasspath │ └── need runtime bytes? -> @Classpath └── no (just a file set) -> @InputFiles + @PathSensitive(weakest that is still correct: NONE < NAME_ONLY < RELATIVE) ``` ## Why 'most permissive that's correct' Every bit of path/name/body you let into the fingerprint is another way to get a needless cache miss or out-of-date result. The relocatable, normalized annotations (`@CompileClasspath` > `@Classpath` > `@InputFiles NONE` ...) maximize cache hits — but only choose one that doesn't *under*-fingerprint, or you risk stale outputs. ## Code ```kotlin abstract class AnalyzeApis : DefaultTask() { @get:CompileClasspath abstract val apiJars: ConfigurableFileCollection @get:Classpath abstract val runtimeJars: ConfigurableFileCollection @get:InputFiles @get:PathSensitive(PathSensitivity.RELATIVE) abstract val configFiles: ConfigurableFileCollection @get:OutputDirectory abstract val report: DirectoryProperty } ``` Pair classpath inputs with settings-level `normalization { runtimeClasspath { ignore(...) } }` to strip any residual volatility.

  • Why prefer @PathSensitive(NONE) over the default when only content matters?
    The default is ABSOLUTE, which bakes machine-specific paths into the fingerprint and breaks relocatability/cache sharing. NONE fingerprints content only, maximizing cache hits while staying correct.
  • Can you layer runtime-classpath normalization on a @Classpath property?
    Yes. settings-level `normalization { runtimeClasspath { ignore(...) } }` applies to runtime classpath fingerprints, letting you strip volatile entries even after choosing @Classpath.

saying these in an interview costs you the question

  • Defaulting to @InputFiles with the implicit ABSOLUTE sensitivity for cacheable tasks.
  • Using @CompileClasspath when the task actually needs runtime bytes (under-fingerprinting → stale outputs).
  • Treating an unordered file set as a classpath and silently relying on order.

context