When would you use apply<PluginClass>() versus apply(plugin = "id"), and what's the trade-off?
answer
- by class = type-safe, needs import
- by id = string, runtime resolution
- class must be on buildscript classpath
- subprojects {} apply by id
- plugins {} preferred for published plugins
basics
~20 sUse 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// 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
Know there are two forms — by class and by ID — and that by-class needs the class available.
Explain the type-safety vs loose-coupling trade-off and the subprojects-by-ID pattern.
Discuss why plugins {} supersedes both for published plugins, and the remaining niche for apply<Class>() with local convention plugins.
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.