skip to content

What is the difference between the foojay-resolver-convention plugin and manually writing a javaRepositories block, and when would you choose the manual form?

level: seniorimportance: should knowfreq 30%

answer

  1. convention auto-registers foojay
  2. plain resolver = classpath only
  3. javaRepositories is ordered
  4. first matching resolver wins
  5. custom JavaToolchainResolver for mirrors

basics

~10 s

The convention plugin auto-registers the Foojay repository for you. Writing javaRepositories manually lets you control resolver ordering, register multiple resolvers, or use a custom resolver.

solid answer

~30 s

`org.gradle.toolchains.foojay-resolver-convention` applies the resolver **and** registers a `foojay` repository in `toolchainManagement` automatically — zero config, the common path. The plain `foojay-resolver` plugin only makes `FoojayToolchainResolver` available on the classpath, leaving you to declare the `javaRepositories { repository("foojay") { resolverClass = ... } }` block yourself. You choose the manual form when you need **ordering** (Gradle tries repositories top-to-bottom and uses the first that supplies a matching JDK), when you want to register **multiple** resolvers (e.g. an internal mirror first, Foojay as fallback), or when registering a **custom** `JavaToolchainResolver`. The manual block gives you explicit, reviewable control over the JDK supply chain.

code

kotlin · 17 lines
kotlin
// settings.gradle.kts — internal mirror first, Foojay fallback
plugins {
    id("org.gradle.toolchains.foojay-resolver") version "0.8.0"
}

toolchainManagement {
    jvm {
        javaRepositories {
            repository("internal") {
                resolverClass = com.acme.InternalMirrorResolver::class.java
            }
            repository("foojay") {
                resolverClass = org.gradle.toolchains.foojay.FoojayToolchainResolver::class.java
            }
        }
    }
}

go deeper

for a junior

Just know the convention plugin sets up Foojay automatically; usually you don't write the block.

for a middle

Distinguish the two plugin ids and note that javaRepositories is an ordered list.

for a senior

Explain ordering, multiple/custom resolvers, and the internal-mirror-first-Foojay-fallback pattern.

for a principal

Drive supply-chain policy: standardize a custom resolver against an internal mirror across the org, drop public Foojay, and audit it via a convention/settings plugin.

## Two flavors of the Foojay plugin The Foojay project ships two settings plugins: | Plugin id | What it does | |---|---| | `org.gradle.toolchains.foojay-resolver-convention` | Applies the resolver **and** registers a `foojay` repository in `toolchainManagement` for you | | `org.gradle.toolchains.foojay-resolver` | Only puts `FoojayToolchainResolver` on the classpath; you register it manually | The convention plugin is the 90% case — most projects just want "download missing JDKs from somewhere sane." ## When manual registration earns its keep `javaRepositories { }` is an **ordered** list. Gradle queries each registered resolver in order and provisions from the first that returns a matching download for the requested spec (version + vendor + implementation). That ordering only matters when you have more than one source: ```kotlin plugins { id("org.gradle.toolchains.foojay-resolver") version "0.8.0" } toolchainManagement { jvm { javaRepositories { repository("internal") { resolverClass = com.acme.InternalMirrorResolver::class.java } repository("foojay") { resolverClass = org.gradle.toolchains.foojay.FoojayToolchainResolver::class.java } } } } ``` Here an internal corporate mirror is tried first; Foojay is the public fallback. The convention plugin can't express this because it registers `foojay` unconditionally. ## Custom resolvers A resolver implements `JavaToolchainResolver`: given a `JavaToolchainRequest` (the spec plus the build platform), it returns an optional `JavaToolchainDownload` carrying a URI. Organizations write these to point provisioning at an authenticated internal artifact store, so build agents never reach out to the public internet for JDKs. You register such a resolver only through the manual `javaRepositories` block. ## Authentication Manual repositories can be paired with credentials supplied via the resolver/plugin so the download URI is fetched with auth headers — important for private mirrors. The convention plugin's public Foojay endpoint needs none. ## Trade-off Convention = brevity and a community default. Manual = explicit, auditable, governable supply chain. In regulated or air-gapped environments the manual form (often with only an internal resolver and no Foojay at all) is the responsible choice.

  • In what order does Gradle consult registered repositories?
    Top-to-bottom as written in javaRepositories. It uses the first resolver that returns a download matching the requested toolchain spec, so put preferred/private sources first.
  • How would you keep build agents from reaching the public Foojay endpoint?
    Register only a custom internal JavaToolchainResolver pointing at your authenticated artifact mirror, and omit the Foojay repository entirely. The manual javaRepositories block is the only way to do this.

saying these in an interview costs you the question

  • Claiming the convention plugin lets you reorder or add resolvers — it doesn't; it registers foojay unconditionally.
  • Thinking foojay-resolver (non-convention) provisions JDKs on its own without a registered repository.

context