skip to content

Plugin<Project> Implementation

Implementing Plugin<Project> and doing the work in apply(): registering tasks and extensions, and applying other plugins programmatically. The canonical 'write me a Gradle plugin' question.

on this pageshow

questions

5

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

level: juniorimportance: must knowfreq 70%

answer

  1. single apply(Project) method
  2. register tasks / create extension / apply plugins
  3. called once at apply time
  4. binary form of build logic
  5. Gradle instantiates the class

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.

solid answer

~40 s

`Plugin<T>` is Gradle's plugin interface; a project plugin implements `Plugin<Project>`. It has one method, `apply(Project project)`, called once when the plugin is applied to a project (via `plugins { id ... }` or `apply<MyPlugin>()`). Inside `apply` you mutate the project model: register tasks with `project.tasks.register(...)`, add an extension with `project.extensions.create(...)`, apply other plugins through `project.pluginManager.apply(...)`, and wire conventions. Gradle instantiates the plugin class (it needs a no-arg constructor, or constructor injection of services like `ObjectFactory`). The plugin is the *binary* form of build logic — compiled, reusable, and testable — as opposed to inline script code. Crucially, `apply` should set up lazy wiring, not eagerly run work.

code

kotlin · 8 lines
kotlin
class GreetingPlugin : Plugin<Project> {
    override fun apply(project: Project) {
        val ext = project.extensions.create("greeting", GreetingExtension::class.java)
        project.tasks.register("hello", HelloTask::class.java) { task ->
            task.message.set(ext.message)
        }
    }
}

go deeper

for a junior

Know that Plugin<Project> has one apply(project) method and that's where you register tasks and extensions.

for a middle

Explain instantiation, idempotent application, and binary-vs-script distinction; prefer lazy register.

for a senior

Discuss constructor service injection, lazy wiring of extension Properties into task inputs, and avoiding eager work in apply.

for a principal

Frame plugins as the org's reusable build-logic surface; standardize a plugin template (extension + lazy tasks + injected services) across teams.

## What a plugin is A Gradle plugin is a reusable, packaged unit of build logic. The contract is the generic interface `org.gradle.api.Plugin<T>`. For build logic targeting a project (the most common case) you implement `Plugin<Project>`. The type parameter `T` is the *target* the plugin configures — `Project` here, but `Settings` and `Gradle` are also valid targets for settings/init plugins. ```kotlin class GreetingPlugin : Plugin<Project> { override fun apply(project: Project) { // configure the project here } } ``` ## The single method: apply The interface has exactly one method: ``` void apply(T target) ``` Gradle calls `apply` **once**, at the moment the plugin is applied to the target. Application happens through the `plugins { }` block (`id "com.example.greeting"`), `apply(plugin = ...)`, or programmatically `project.pluginManager.apply(GreetingPlugin::class.java)`. Re-applying the same plugin type is idempotent — Gradle tracks applied plugins and won't run `apply` twice. ## What you do inside apply Three canonical jobs: 1. **Register tasks** — `project.tasks.register("hello", HelloTask::class.java)`. Use `register` (lazy) not `create` (eager). 2. **Add an extension** — `project.extensions.create("greeting", GreetingExtension::class.java)` to give users a configuration DSL block. 3. **Apply other plugins** — `project.pluginManager.apply("java")` so your plugin builds on top of existing capabilities. You typically also wire the extension's lazy `Property`/`Provider` values into the task inputs so user configuration flows to tasks without ordering problems. ## Instantiation Gradle constructs the plugin instance. A no-argument constructor works, or you can declare a constructor with `@Inject`-able services such as `ObjectFactory`, `ProviderFactory`, or `ProjectLayout`. Gradle supplies those. ## Binary vs script plugins This class form is a **binary plugin**: compiled Kotlin/Java/Groovy living in `buildSrc`, an included build, or a published artifact. It is type-safe, unit-testable (e.g. with ProjectBuilder), and reusable across projects — the preferred way to share non-trivial build logic.

  • Why prefer tasks.register over tasks.create inside apply?
    register is lazy (configuration avoidance): the task is only configured/instantiated if it's actually needed for the build, keeping configuration time low. create eagerly realizes the task every build.
  • Does apply run for every project automatically?
    No. apply runs only on the project(s) the plugin is applied to. Applying to the root project does not cascade to subprojects unless you explicitly iterate subprojects or each project applies it.

Think of apply() as a setup wizard that runs once: it installs the tasks, exposes the settings panel (extension), and pulls in dependencies (other plugins) — but it shouldn't actually do the heavy work itself; the tasks it registers do that later.

saying these in an interview costs you the question

  • Saying apply() does the build work itself — it should register tasks, not execute them.
  • Confusing Plugin<Project> with Plugin<Settings>; the target type matters.
  • Using tasks.create eagerly when register is the modern lazy default.

context

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

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

What kinds of work should you avoid doing eagerly in Plugin<Project>.apply, and what should you do instead?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Don't do expensive or side-effecting work in apply — no I/O, no resolving configurations, no eager tasks.create or forcing Property.get(). Register tasks lazily, wire Providers, and defer real work to task actions executed only when needed.

open as a page

How are services like ObjectFactory and ProjectLayout made available to a Plugin<Project>, and why use them instead of Project methods directly?

level: seniorimportance: should knowfreq 40%

basics

~10 s

Gradle injects services into a plugin via an @Inject constructor — e.g. ObjectFactory, ProjectLayout, ProviderFactory. You use them to create Property/Provider instances and resolve paths lazily, which is cleaner and configuration-cache friendly.

open as a page