skip to content

Signing

Signing published artifacts: applying the signing plugin, supplying keys locally and in CI, and requiring signatures only where they matter. Interviewers ask because signing is mandatory for Central and awkward to automate.

on this pageshow

explore

questions

15

What does the `isRequired` property in Gradle's signing block control, and why would you set it conditionally instead of leaving it at its default?

level: juniorimportance: must knowfreq 55%

answer

  1. isRequired = fail-when-cannot-sign
  2. default true
  3. false => warn + skip, unsigned artifacts
  4. predicate: hasTask('publish') && !SNAPSHOT
  5. lets local/dev builds pass without a key

basics

~20 s

isRequired decides whether a missing or failing signature aborts the build. By default it's true. You set it conditionally (e.g. only for releases) so local or SNAPSHOT builds don't fail when no signing key is configured.

solid answer

~40 s

The signing plugin's `isRequired` (Groovy: `required`) flag controls what happens when a signature can't be produced — typically because no key is configured. When `true` (the default), the `sign*` tasks fail the build if signing materials are missing or signing throws. When `false`, the tasks log a warning and skip, leaving artifacts unsigned. You set it conditionally so that everyday developer builds and SNAPSHOT publishes (which usually have no GPG key on the machine) succeed, while release publishes to Maven Central — which *must* be signed — still fail loudly if the key is absent. The canonical predicate is `isRequired = gradle.taskGraph.hasTask('publish') && !version.endsWith('-SNAPSHOT')`, evaluated lazily after the task graph is ready.

code

kotlin · 5 lines
kotlin
signing {
    isRequired = gradle.taskGraph.hasTask("publish") &&
        !version.toString().endsWith("-SNAPSHOT")
    sign(publishing.publications["maven"])
}

go deeper

for a junior

Know that true means the build fails if it can't sign, and false lets it pass unsigned; conditional so local builds without a key don't break.

for a middle

Explain the canonical predicate and that SNAPSHOTs/dev builds skip while releases require signatures.

for a senior

Discuss lazy evaluation against the task graph and that materials-present still signs even when not required.

for a principal

Frame it as a release-safety gate: unsigned releases must fail fast; tie to org-wide publishing policy and CI key provisioning.

## What `isRequired` does The `signing` plugin adds `Sign` tasks (e.g. `signMavenPublication`) that produce detached `.asc` signatures for your artifacts. Each `Sign` task — and the extension as a whole — exposes a boolean `isRequired` (Kotlin DSL) / `required` (Groovy DSL). - `isRequired = true` (the default): if signing **cannot** happen — no key configured, bad passphrase, signatory throws — the build **fails**. - `isRequired = false`: the same failure is downgraded to a logged warning, the `Sign` task is effectively skipped, and the build continues with **unsigned** artifacts. Note a subtlety: `isRequired` governs the *failure-when-impossible* behavior, not whether signing is attempted. If signing materials *are* present, signing still runs even when `isRequired = false`. ## Why make it conditional Two realities collide: 1. **Local/CI dev builds and SNAPSHOTs** usually run on machines with no GPG key. Requiring signatures there breaks `./gradlew build` for everyone. 2. **Release publishing to Maven Central** *requires* a valid PGP signature — an unsigned release silently slipping through is a release-blocking defect you want to catch at build time, not at upload. So you flip `isRequired` based on intent: require it only when the build is actually a release publish. ## The canonical predicate ```kotlin signing { isRequired = gradle.taskGraph.hasTask("publish") && !version.toString().endsWith("-SNAPSHOT") } ``` Two conditions: - `gradle.taskGraph.hasTask("publish")` — true only when a `publish` task is in the execution graph, so it doesn't fire for `build`/`assemble`. - `!version.endsWith("-SNAPSHOT")` — true only for release versions, so SNAPSHOT publishes stay unsigned. ## Lazy evaluation matters `gradle.taskGraph` is only populated **after** the configuration phase, during `whenReady`. Because `signing { isRequired = ... }` is evaluated lazily by the plugin when the `Sign` task's `required` provider is queried at execution time, querying `hasTask` works. If you instead read it too eagerly you can get a 'task graph not ready' error — see the follow-ups.

  • If `isRequired` is false but a valid key IS configured, are artifacts signed?
    Yes. `isRequired` only controls the failure behavior when signing is impossible. If materials are present, the Sign task still runs and produces signatures.
  • Why can reading `gradle.taskGraph.hasTask(...)` throw if done in the wrong place?
    The task graph is only ready after configuration. If the predicate is evaluated during configuration rather than lazily at execution, the graph isn't populated yet and Gradle complains. The signing plugin resolves `required` lazily, so the standard idiom works.

saying these in an interview costs you the question

  • Saying isRequired controls *whether signing runs at all* — it controls failure-when-impossible, not attempt.
  • Claiming the default is false (it's true).

context

open as a page

At a basic level, what does the Gradle signing plugin produce, and why does publishing to Maven Central require it?

level: juniorimportance: must knowfreq 40%

basics

~10 s

It produces a detached PGP signature (.asc file) for each artifact (jar, POM, sources, javadoc). Maven Central requires these signatures so consumers can verify the artifacts are authentic and untampered.

open as a page

What does the Gradle `signing` plugin do, and why do you need it when publishing to Maven Central?

level: juniorimportance: must knowfreq 60%

basics

~10 s

The signing plugin uses PGP/GPG to create detached .asc signature files for your build artifacts. Maven Central requires every published file to be signed so consumers can verify authenticity.

open as a page

Walk through how `gradle.taskGraph.hasTask('publish')` works in a conditional signing predicate and why the task graph readiness matters.

level: middleimportance: must knowfreq 45%

basics

~20 s

gradle.taskGraph is the resolved DAG of tasks Gradle will run. hasTask('publish') returns true only when a publish task is scheduled. The graph is built after configuration, so the predicate must be read lazily at execution time, not during configuration.

open as a page

The Gradle signing plugin can obtain a PGP signing key in two main ways: useGpgCmd() and useInMemoryPgpKeys(...). What is the difference between these two approaches and when would you reach for each?

level: middleimportance: must knowfreq 55%

basics

~10 s

useGpgCmd() shells out to the local gpg binary and its keyring/agent. useInMemoryPgpKeys() takes the raw ASCII-armored key text and passphrase directly, so no gpg install or keyring is needed — ideal for CI.

open as a page

Walk me through configuring `signing { sign(publishing.publications["maven"]) }`. What gets signed, and how do the signatures end up published?

level: middleimportance: must knowfreq 50%

basics

~20 s

Inside the signing { } block you call sign(...) passing the publication. That signs every artifact in that publication — jars and POM — and adds each .asc to the publication so publish uploads them.

open as a page

How do you apply the signing plugin, and what tasks does it create and hook into the build lifecycle?

level: juniorimportance: should knowfreq 30%

basics

~20 s

Apply it with plugins { signing }. It adds a signing { } block and creates a Sign task per signed publication (e.g. signMavenPublication), which the publish tasks depend on so signing runs before upload.

open as a page

Why does the conditional signing predicate include `!version.endsWith('-SNAPSHOT')`, and what would go wrong if you omitted it?

level: middleimportance: should knowfreq 38%

basics

~20 s

SNAPSHOTs are mutable pre-release builds that repositories don't require to be signed. The -SNAPSHOT check skips signing for them so frequent dev publishes don't need a key, while only immutable release versions are forced to sign.

open as a page

When using useGpgCmd(), how does Gradle know which key to use and how does the passphrase get supplied? What properties are involved?

level: middleimportance: should knowfreq 28%

basics

~10 s

useGpgCmd() invokes the local gpg binary. The signing.gnupg.keyName property selects the key; the gpg-agent normally supplies/caches the passphrase, or you can set signing.gnupg.passphrase. Other signing.gnupg.* properties tune the executable and home dir.

open as a page

useInMemoryPgpKeys has a two-argument form and a form that also takes a key id. Why does the key-id overload exist, and when do you need it?

level: middleimportance: should knowfreq 30%

basics

~20 s

An armored export can contain a master key plus several subkeys. The 2-arg form uses the first/default key; the keyId overload lets you pick a specific (sub)key by id when more than one is present.

open as a page

What exactly is a detached `.asc` signature, and how does Gradle make sure it gets uploaded alongside the artifact?

level: middleimportance: should knowfreq 35%

basics

~10 s

A detached .asc is a separate ASCII-armored PGP signature file sitting next to the artifact (e.g. lib.jar.asc). The signing plugin registers each .asc as a publication artifact, so publish uploads it automatically.

open as a page

How would you design a conditional-signing setup for a library published from CI, balancing local developer builds, snapshot CI, and signed release CI?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Make isRequired true only for release publishes (hasTask('publish') && !SNAPSHOT). Provide the signing key/passphrase only to the release CI job via secrets; leave local and snapshot builds keyless so they pass with unsigned (or skipped) artifacts.

open as a page

You need to sign artifacts in CI using useInMemoryPgpKeys but the ASCII-armored key has newlines that your CI secret store mangles. How do you reliably get the key and passphrase into the Gradle build?

level: seniorimportance: should knowfreq 45%

basics

~10 s

Base64-encode the armored key so it's a single-line value, store it (and the passphrase) as a CI secret, then decode it in the build and pass both to useInMemoryPgpKeys(decodedKey, password).

open as a page

You're setting up signed publishing in CI for a library that releases to Maven Central. How do you make sure every release artifact is correctly signed, and how would you validate it before the upload actually hits Central?

level: seniorimportance: should knowfreq 28%

basics

~20 s

Sign the publication so every artifact (incl. POM) gets a .asc, then run signMavenPublication plus publishToMavenLocal in CI and gpg --verify the outputs as a gate before the real upload. Keep key material out of the repo, injected via CI secrets.

open as a page

What's the difference between making signing conditional via `isRequired` versus disabling the Sign task with `onlyIf { ... }` or `enabled = false`? When would you use each?

level: seniorimportance: nice to knowfreq 20%

basics

~20 s

isRequired controls whether a missing signature fails the build but still signs when a key exists. onlyIf/enabled=false skips the Sign task entirely — no signature produced even with a key. Use isRequired to relax failure; use onlyIf to truly skip signing.

open as a page