skip to content

When would you use apply<PluginClass>() versus apply(plugin = "id"), and what's the trade-off?

level: middleimportance: should knowfreq 40%

answer

  1. by class = type-safe, needs import
  2. by id = string, runtime resolution
  3. class must be on buildscript classpath
  4. subprojects {} apply by id
  5. plugins {} preferred for published plugins

basics

~20 s

Use apply<MyPlugin>() to apply a plugin by its class when you have the class on the classpath (e.g. a buildSrc or included-build plugin). Use apply(plugin = "id") to apply by string ID when you only know the ID.

solid answer

~50 s

`apply<PluginClass>()` applies a plugin by its concrete `Plugin<Project>` type, which requires the class to be on the build-script classpath — typical for plugins from `buildSrc`, an included build, or `buildscript` classpath. It's type-safe: a typo is a compile error, and you can reference the exact implementation. `apply(plugin = "id")` applies by the registered plugin **ID** string, resolved at runtime via the plugin descriptor; it's needed when you only have the ID (e.g. a third-party plugin you don't want to import) or are applying inside `subprojects {}` where you reference IDs. Trade-off: `apply<Class>()` gives compile-time safety but couples you to the class and requires the import; `apply(plugin = "id")` is loosely coupled and works without importing the type, at the cost of runtime-only failure on typos. Modern code prefers the `plugins {}` DSL by ID for both safety and resolution; `apply<Class>()` mostly survives for local convention plugins.

code

kotlin · 9 lines
kotlin
// By class — compile-time checked, class must be on the classpath
import com.example.build.QualityConventions
apply<QualityConventions>()

// By ID — string lookup, resolved at runtime
subprojects {
    apply(plugin = "java-library")
    apply(plugin = "jacoco")
}

go deeper

for a junior

Know there are two forms — by class and by ID — and that by-class needs the class available.

for a middle

Explain the type-safety vs loose-coupling trade-off and the subprojects-by-ID pattern.

for a senior

Discuss why plugins {} supersedes both for published plugins, and the remaining niche for apply<Class>() with local convention plugins.

for a principal

Define standards: give convention plugins real IDs so teams use plugins {}, reserving apply<Class>()/apply(plugin=) for genuinely dynamic wiring; weigh maintainability across many subprojects.

## Two legacy imperative forms Legacy application has two flavours, both methods on `PluginAware`: ### By class — apply<Class>() ```kotlin import com.example.MyConventionPlugin apply<MyConventionPlugin>() ``` Gradle instantiates the given `Plugin` type and calls its `apply(project)`. Requirements and traits: - The class **must** be on the build-script classpath (compiled `buildSrc`, an `includeBuild` plugin, or a `buildscript` classpath JAR). - **Type-safe**: misspelling the class name fails at compile time in Kotlin DSL. - Couples the script to the concrete implementation — you need the import and the type must be public. - Common for **convention plugins** you author locally before they're packaged with their own ID. ### By ID — apply(plugin = "id") ```kotlin apply(plugin = "java-library") apply(plugin = "com.diffplug.spotless") ``` Gradle looks up the ID in the plugin descriptors (`META-INF/gradle-plugins/<id>.properties`) available on the classpath and applies the mapped implementation. Traits: - Works with only the **string ID**; no import of the implementation type needed. - **Runtime resolution**: a typo or missing classpath entry fails only when the build runs. - The natural choice inside `subprojects {}`/`allprojects {}` blocks where you wire many projects by ID. ## How to choose | Situation | Prefer | |---|---| | Local plugin class from buildSrc/includeBuild | `apply<Class>()` (type-safe) or, better, give it an ID and use `plugins {}` | | Third-party plugin, only ID known | `apply(plugin = "id")` or `plugins {}` | | Applying to other projects programmatically | `apply(plugin = "id")` | | New code | `plugins {}` DSL by ID | ## Why plugins {} usually wins The `plugins {}` DSL by ID gives you both worlds for published/precompiled plugins: declarative resolution **and** type-safe extension accessors generated for the rest of the script. `apply<Class>()` does not generate those accessors. So `apply<Class>()` lingers mainly where a plugin has no ID yet, or for very dynamic application logic. ## Gotcha `apply<Class>()` requires the type be accessible (public, on classpath). A `private` plugin class in `buildSrc` won't be visible. And applying the same plugin twice is idempotent — Gradle won't re-run it.

  • Why can't you always use plugins {} instead of apply<Class>()?
    plugins {} resolves plugins by ID via repositories/markers. A local class without a registered ID (or very dynamic conditional application) can only be applied via apply<Class>() or apply(plugin=).
  • What happens if you apply the same plugin twice?
    Application is idempotent — Gradle tracks applied plugins and won't re-run apply() for an already-applied plugin.
  • Which form catches a typo earliest?
    apply<Class>() — a wrong class name is a Kotlin compile error, whereas apply(plugin="typo") fails only at runtime.

saying these in an interview costs you the question

  • Saying apply<Class>() resolves the plugin from a repository — it doesn't; the class must already be on the classpath.
  • Claiming apply<Class>() generates type-safe extension accessors like plugins {} does.

context