skip to content

Authoring Plugins

Writing Gradle plugins: project and settings plugins, plugin id metadata, precompiled convention plugins, publishing, and TestKit functional tests. Interviewers ask because plugins are how build logic gets shared and versioned.

on this pageshow

questions

page 1 of 2

How do you declare a plugin id for a custom Gradle plugin using the java-gradle-plugin plugin, and what is the role of implementationClass?

level: juniorimportance: must knowfreq 70%

answer

  1. apply java-gradle-plugin
  2. gradlePlugin { plugins { create … } }
  3. id + implementationClass
  4. generates META-INF/gradle-plugins/<id>.properties
  5. Plugin<Project>.apply called on apply

basics

~20 s

Apply the java-gradle-plugin plugin, then in the gradlePlugin { plugins { … } } block register a plugin entry with an id and an implementationClass — the fully-qualified name of your Plugin<Project> class that Gradle instantiates when that id is applied.

solid answer

~40 s

You apply the `java-gradle-plugin` plugin to your build-logic project. It exposes the `gradlePlugin` extension where you register named plugin entries: ```kotlin gradlePlugin { plugins { create("greeting") { id = "com.acme.greeting" implementationClass = "com.acme.GreetingPlugin" } } } ``` `id` is the public identifier consumers use in `plugins { id("com.acme.greeting") }`. `implementationClass` is the fully-qualified name of the class implementing `Plugin<Project>` (or `Plugin<Settings>`). When a build applies the id, Gradle reads the descriptor, finds this class and calls its `apply` method. The `java-gradle-plugin` plugin also auto-generates the plugin descriptor file and a plugin marker artifact so the id can be resolved from a repository.

code

kotlin · 12 lines
kotlin
plugins {
  `java-gradle-plugin`
}

gradlePlugin {
  plugins {
    create("greeting") {
      id = "com.acme.greeting"
      implementationClass = "com.acme.GreetingPlugin"
    }
  }
}

go deeper

for a junior

Recall that you apply java-gradle-plugin and set id + implementationClass in gradlePlugin { plugins { … } }.

for a middle

Explain the generated descriptor file and that the entry name differs from the id; show the Kotlin DSL.

for a senior

Discuss why id and class are decoupled, reverse-DNS naming, and what the descriptor contract means at apply time.

for a principal

Frame id conventions as an org-wide contract (namespacing, stability across renames) and how that affects internal plugin catalogs.

## What a plugin id is A Gradle plugin needs a stable, public **id** so build scripts can apply it with `plugins { id("...") }`. The id is decoupled from the implementation class name on purpose: the class can move or be renamed without breaking consumers, and one published module can expose several ids. ## The `java-gradle-plugin` plugin Applying `java-gradle-plugin` to a JVM project does three things: 1. Adds the `gradleApi()` dependency to `implementation` so you can compile against Gradle types. 2. Adds the `gradlePlugin` extension (a `GradlePluginDevelopmentExtension`) for declaring plugins. 3. Wires tasks that generate the **plugin descriptor** and **plugin marker** artifacts. ## Declaring a plugin ```kotlin gradlePlugin { plugins { create("greeting") { // the NamedDomainObjectContainer entry name (arbitrary) id = "com.acme.greeting" // public id consumers apply implementationClass = "com.acme.GreetingPlugin" } } } ``` - The container entry name (`"greeting"`) is internal bookkeeping; it is **not** the id. - `id` must be a valid plugin id: lowercase, dot-separated namespaces, typically reverse-DNS to avoid collisions. - `implementationClass` is the fully-qualified class name implementing `Plugin<Project>`. ## The descriptor file From each entry, the `java-gradle-plugin` generates `META-INF/gradle-plugins/<id>.properties` on the classpath containing one line: ``` implementation-class=com.acme.GreetingPlugin ``` This is the actual runtime contract: when a build applies `com.acme.greeting`, Gradle looks up `com/acme/greeting` → wait, it looks up `META-INF/gradle-plugins/com.acme.greeting.properties`, reads `implementation-class`, loads that class and instantiates it. ## Why use the DSL instead of hand-writing the descriptor Before `java-gradle-plugin` you hand-authored the `.properties` file. The DSL is preferred because it also generates **plugin marker artifacts** (needed for `plugins {}` resolution from a repository) and feeds the metadata into validation and publishing tasks.

  • What file does java-gradle-plugin generate from a plugin declaration, and what does it contain?
    A descriptor at META-INF/gradle-plugins/<id>.properties containing a single line implementation-class=<fully-qualified class>. Gradle reads it at apply time to find and instantiate the plugin class.
  • Is the name passed to create("greeting") the same as the plugin id?
    No. That name is just the container entry key for build-logic bookkeeping. The public id is the separate id property you assign inside the block.

saying these in an interview costs you the question

  • Confusing the container entry name with the public plugin id.
  • Hand-writing the META-INF descriptor when java-gradle-plugin already generates it.
  • Claiming implementationClass is the id rather than the FQN of the Plugin<Project> class.

context

open as a page

What is a precompiled script convention plugin in Gradle, and what problem does it solve?

level: juniorimportance: must knowfreq 70%

basics

~10 s

It's a *.gradle.kts file placed in a build-logic project's source set that Gradle compiles into a plugin. You apply it by id to share common build configuration across many subprojects instead of copy-pasting it.

open as a page

What is Gradle's ProjectBuilder and what do you use it for when developing a plugin?

level: juniorimportance: must knowfreq 55%

basics

~10 s

ProjectBuilder creates a throwaway Project instance in a JVM unit test (ProjectBuilder.builder().build()), so you can apply your plugin and assert it wired up tasks, extensions or configurations — without running a real build.

open as a page

What is the Plugin<Project> interface in Gradle, and what does the apply method do?

level: juniorimportance: must knowfreq 70%

basics

~10 s

Plugin<Project> is the interface a Gradle plugin implements. Its single apply(project) method runs when the plugin is applied and is where you configure the project: register tasks, add extensions, or apply other plugins.

open as a page

What is a Settings plugin in Gradle, and how does it differ from a Project plugin?

level: juniorimportance: must knowfreq 55%

basics

~10 s

A Settings plugin implements Plugin<Settings> and is applied in settings.gradle(.kts). It configures the build as a whole (which projects exist, repositories, plugin resolution), unlike a Project plugin that configures one project's tasks and dependencies.

open as a page

What is Gradle TestKit and what kind of plugin testing is it for?

level: juniorimportance: must knowfreq 60%

basics

~10 s

TestKit is a Gradle library for functional testing of plugins. You run a real Gradle build in a temporary project directory via GradleRunner and assert on the result, instead of mocking the build.

open as a page

Why does Gradle require input/output annotations on task properties, and what breaks if validation is ignored?

level: juniorimportance: must knowfreq 48%

basics

~20 s

Annotations like @Input and @OutputFile tell Gradle which properties are inputs and outputs so it can do up-to-date checking and caching. Without them, Gradle may skip a task whose inputs actually changed, producing stale outputs.

open as a page

What are Gradle plugin marker artifacts, and why are they needed for resolving plugins via the plugins {} block from a custom repository?

level: middleimportance: must knowfreq 55%

basics

~20 s

A plugin marker is a tiny published module whose coordinates are derived from the plugin id (id:id.gradle.plugin) and whose only job is to declare a dependency on the real plugin jar. It lets plugins {} resolve a plugin by id rather than by Maven coordinates.

open as a page

How do you publish a Gradle plugin to the public Gradle Plugin Portal? Walk through the plugin and task involved.

level: middleimportance: must knowfreq 55%

basics

~10 s

Apply the com.gradle.plugin-publish plugin, configure plugin metadata under the gradlePlugin block, set your Portal API key/secret, then run the publishPlugins task.

open as a page

What metadata must you configure before `publishPlugins` will accept a plugin, and where does each piece go in the build script?

level: middleimportance: must knowfreq 45%

basics

~10 s

On the gradlePlugin extension: website and vcsUrl. On each plugin entry: id, implementationClass, displayName, description, and tags. The Portal rejects publishes missing these.

open as a page

How do you apply and configure other plugins inside a precompiled convention plugin, and how do you get type-safe accessors for them?

level: middleimportance: must knowfreq 55%

basics

~20 s

Request them in the convention plugin's own plugins {} block. The kotlin-dsl plugin then generates type-safe accessors so you can configure them with java { }, kotlin { }, etc. Their plugin coordinates must be on the build-logic project's classpath.

open as a page

Show how you'd use ProjectBuilder to assert that your plugin registered a task, added an extension, and created a configuration.

level: middleimportance: must knowfreq 45%

basics

~10 s

Build a Project, apply the plugin, then assert: project.tasks.findByName("x"), project.extensions.findByName("y"), and project.configurations.findByName("z") are non-null. Each check confirms a piece of configuration-phase wiring.

open as a page

When would you choose ProjectBuilder over TestKit for testing a Gradle plugin, and what are the trade-offs?

level: middleimportance: must knowfreq 50%

basics

~10 s

Use ProjectBuilder for fast, in-process unit tests that check configuration wiring (tasks/extensions registered). Use TestKit when you need to run a real build out-of-process and assert on execution, task outcomes, or build output.

open as a page

Inside a Plugin<Project>.apply method, how do you register a task and an extension, and how do you connect them?

level: middleimportance: must knowfreq 65%

basics

~10 s

Use project.extensions.create("name", Ext::class.java) to add a config block and project.tasks.register("task", Task::class.java) to register a task. Connect them by setting the task's Property inputs from the extension's Provider values.

open as a page

What can a Settings plugin do with the pluginManagement block, and why is it the only place to do it?

level: middleimportance: must knowfreq 50%

basics

~10 s

pluginManagement controls where plugins are resolved (repositories), version/ID resolution rules (resolutionStrategy), and can includeBuild a build that supplies plugins. It must be in settings because plugin resolution happens during initialization, before build scripts run.

open as a page

How do you assert on task results from a BuildResult, and what do the different TaskOutcome values mean?

level: middleimportance: must knowfreq 50%

basics

~10 s

BuildResult.task(":path").outcome returns a TaskOutcome enum: SUCCESS, UP_TO_DATE, FROM_CACHE, SKIPPED, NO_SOURCE, FAILED. You assert the expected outcome plus check result.output for messages.

open as a page

How does withPluginClasspath() make the plugin under test available to the build, and how do you set it up?

level: middleimportance: must knowfreq 55%

basics

~10 s

withPluginClasspath() injects the plugin-under-test's classes onto the build's classpath so plugins { id("...") } resolves without publishing. The java-gradle-plugin plugin generates this classpath automatically.

open as a page

How do you make plugin validation problems fail the build instead of just warning?

level: middleimportance: must knowfreq 50%

basics

~10 s

Configure the ValidatePlugins task with failOnWarning = true. Then any TypeValidationProblem becomes a build failure rather than a warning, so missing annotations break the build.

open as a page

What does the validatePlugins task do, and when does it run in a plugin build?

level: middleimportance: must knowfreq 55%

basics

~10 s

validatePlugins is a task added by the java-gradle-plugin. It inspects your custom task and plugin types and reports problems such as task properties missing input/output annotations.

open as a page

How do you declare multiple plugin ids from a single build-logic project, and what gets generated for each?

level: middleimportance: should knowfreq 35%

basics

~20 s

Add several create("…") entries inside gradlePlugin { plugins { … } }, each with its own id and implementationClass. Each entry produces its own descriptor file and, when publishing, its own marker artifact, all backed by the same implementation jar.

open as a page

Where do the group and version metadata for a published plugin come from, and how do they relate to the plugin id and marker?

level: middleimportance: should knowfreq 40%

basics

~20 s

The implementation module's coordinates come from the project's group and version properties. The version requested in plugins {} must match the marker version, which derives from the same project version. The id is independent of group.

open as a page

How do you supply Gradle Plugin Portal credentials securely, especially from CI, and what are the common pitfalls?

level: middleimportance: should knowfreq 30%

basics

~10 s

Use the Portal API key and secret as gradle.publish.key/gradle.publish.secret from ~/.gradle/gradle.properties locally, or from env vars GRADLE_PUBLISH_KEY/GRADLE_PUBLISH_SECRET injected as CI secrets. Never commit them.

open as a page

What rules and conventions govern the plugin ID you publish to the Portal, and how do they affect namespace ownership?

level: middleimportance: should knowfreq 25%

basics

~10 s

Plugin IDs are reverse-domain dotted names (e.g. com.example.greeting). On the Portal the namespace (leading segments) is owned by the first publisher, so you must publish under a domain/group you control.

open as a page

What are the rules and gotchas around file placement, packages, and ids for precompiled convention plugins?

level: middleimportance: should knowfreq 35%

basics

~20 s

Place *.gradle.kts files in src/main/kotlin of a kotlin-dsl project. The plugin id is the filename minus .gradle.kts. A package declaration namespaces the generated class but does not change the id; the dotted filename itself forms the id.

open as a page

How do precompiled convention plugins differ from legacy `apply from:` script plugins, and why are they preferred?

level: middleimportance: should knowfreq 50%

basics

~20 s

apply from: includes a raw .gradle.kts file at configuration time — uncompiled, untyped, no plugin id. Precompiled convention plugins are compiled by kotlin-dsl into binary plugins with type-safe accessors, applied via the plugins {} block by id.

open as a page

How does a Plugin<Project> apply another plugin, and how should it react when a specific plugin is (or isn't) present?

level: middleimportance: should knowfreq 50%

basics

~10 s

Use project.pluginManager.apply("java") (or the plugin class) to apply another plugin. To react to a plugin without forcing it, use project.pluginManager.withPlugin("...") or plugins.withType(JavaPlugin::class.java) { ... }.

open as a page

How does a Settings plugin use dependencyResolutionManagement to centralize repositories and version catalogs?

level: middleimportance: should knowfreq 45%

basics

~10 s

dependencyResolutionManagement in settings declares repositories for all projects and defines version catalogs. A settings plugin calls settings.dependencyResolutionManagement {} to set repositoriesMode, central repositories, and create catalogs like libs once for the whole build.

open as a page

How do you test that your plugin fails the build correctly, and how do you assert on failure output?

level: middleimportance: should knowfreq 40%

basics

~10 s

Call buildAndFail() instead of build(). It returns a BuildResult only when the build fails, otherwise it throws. Then assert the failing task's outcome is FAILED and that result.output contains your error message.

open as a page

From an architecture standpoint, why does Gradle separate the public plugin id from the implementationClass, and what does that decoupling buy a large organization maintaining many internal plugins?

level: seniorimportance: should knowfreq 25%

basics

~20 s

Separating id from class makes the id a stable public contract while the implementation can be refactored, renamed, or repackaged freely. For an org, ids become a governable namespace independent of code layout, and the marker layer lets repositories resolve them uniformly.

open as a page

What is a 'plugin marker' artifact, and why does the Plugin Portal publish one alongside your implementation JAR?

level: seniorimportance: should knowfreq 35%

basics

~10 s

A marker is a tiny artifact at <pluginId>:<pluginId>.gradle.plugin whose POM just depends on your real implementation JAR. It lets the plugins { id … } block map a plugin ID to Maven coordinates.

open as a page

showing 1–30 of 44