skip to content

How do @Classpath and @CompileClasspath differ from @PathSensitive(RELATIVE) for declaring classpath-style inputs, and when do you use each?

level: seniorimportance: must knowfreq 45%

answer

  1. @Classpath = ordered runtime cp, jar-normalized
  2. @CompileClasspath = ABI-only, enables compile avoidance
  3. don't combine with @PathSensitive
  4. ABI = public signatures + constants
  5. runtime vs compile role

basics

~10 s

@Classpath fingerprints a list of jars by content but ignores order-insensitive path noise and applies jar-aware normalization. @CompileClasspath goes further, hashing only the public ABI of classes so non-API changes don't bust the cache.

solid answer

~40 s

For ordinary file inputs you use `@PathSensitive`. For classpath inputs there are dedicated annotations that encode classpath semantics. `@Classpath` marks a `@InputFiles` property as a runtime classpath: order *is* significant, but Gradle normalizes inside jars and across directory layout so equivalent classpaths fingerprint equally. `@CompileClasspath` is stricter and smarter — it fingerprints only the **ABI** (public types, signatures, constants) of each class, so changing a private method body or recompiling with no API change keeps the fingerprint stable and avoids recompiling downstream modules. Use `@CompileClasspath` for inputs feeding `javac`/`kotlinc` compilation, `@Classpath` for runtime classpaths (tests, packaging, launching). Plain `@PathSensitive(RELATIVE)` is for non-classpath file trees where order and ABI semantics don't apply.

code

kotlin · 10 lines
kotlin
abstract class PackageTask : DefaultTask() {
    @get:CompileClasspath
    abstract val apiClasspath: ConfigurableFileCollection

    @get:Classpath
    abstract val runtimeClasspath: ConfigurableFileCollection

    @get:OutputFile
    abstract val archive: RegularFileProperty
}

go deeper

for a junior

Recognize that @Classpath and @CompileClasspath exist for jar/classpath inputs rather than plain @InputFiles.

for a middle

Explain that @CompileClasspath uses ABI-only fingerprinting and @Classpath is order-sensitive and jar-normalized.

for a senior

Choose correctly between the two by input role, explain compile avoidance, and know they exclude @PathSensitive.

for a principal

Audit custom plugins org-wide so compile-feeding inputs use @CompileClasspath, maximizing compile avoidance across a large multi-module graph.

## The problem these annotations solve A classpath is an *ordered* list of jars and class directories. Treating it as a generic file collection wastes cache hits: a recompiled jar with an identical public API still has a different byte-for-byte content (timestamps, debug info, private bodies), so a naive fingerprint changes and everything downstream re-runs. Gradle provides classpath-aware annotations that fingerprint by *meaning* instead of raw bytes. ## @Classpath Applied to a file-collection input that represents a **runtime** classpath. Semantics: - **Order matters** — `[a.jar, b.jar]` fingerprints differently from `[b.jar, a.jar]`, because runtime resolution order can change behavior. - Path noise is ignored (relative/absolute location of the jars doesn't matter). - Inside each jar, entries are normalized so two jars with the same logical contents but different packaging metadata fingerprint equally. ## @CompileClasspath A stricter form for inputs that feed a **compiler**. On top of `@Classpath` behavior it performs **ABI (Application Binary Interface) extraction**: it hashes only the parts of a `.class` file a downstream compiler can observe — public/protected type and member signatures, constant values — and ignores private members, method bodies, and debug info. Result: editing a method body in an upstream module does *not* change the compile-classpath fingerprint of consumers, so they stay up-to-date. This is the engine behind compile avoidance. ## When to use which | Input role | Annotation | |---|---| | Files feeding `javac`/`kotlinc` as the compile classpath | `@CompileClasspath` | | Runtime classpath for tests, `application`, packaging | `@Classpath` | | A non-classpath file tree (resources, templates, sources) | `@PathSensitive(RELATIVE/NONE/...)` | ```kotlin abstract class RunTool : DefaultTask() { @get:CompileClasspath abstract val compileCp: ConfigurableFileCollection // ABI-only fingerprint @get:Classpath abstract val runtimeCp: ConfigurableFileCollection // ordered, jar-normalized @get:InputFiles @get:PathSensitive(PathSensitivity.RELATIVE) abstract val resources: ConfigurableFileCollection // plain tree } ``` ## Gotcha `@Classpath`/`@CompileClasspath` are mutually exclusive with `@PathSensitive` on the same property — they *imply* their own normalization. Don't stack them. And they only make sense on inputs that really are classpaths; using `@Classpath` on a plain resource tree silently applies the wrong (order-sensitive, jar-aware) semantics.

  • Why can @CompileClasspath let a downstream module stay up-to-date even after the upstream module is recompiled?
    It fingerprints only the ABI. If the recompile didn't change any public signature or constant, the consumer's compile-classpath fingerprint is unchanged, so its compile task stays up-to-date — that's compile avoidance.
  • Is order significant for @Classpath?
    Yes. Runtime classpath order can change which class wins, so reordering jars produces a different fingerprint — unlike a plain @PathSensitive(NONE) collection.
  • Can you put both @Classpath and @PathSensitive on one property?
    No. The classpath annotations carry their own normalization; combining them is invalid. Pick one based on the input's role.

saying these in an interview costs you the question

  • Saying @Classpath ignores order — order is significant for runtime classpaths.
  • Claiming @CompileClasspath hashes full class bytes — it hashes only the ABI.
  • Stacking @PathSensitive on a @Classpath property.

context