Gradle had a legacy Convention object (project.convention / the Convention API). How does it relate to the modern extension/convention-property model, and what replaced it?
answer
- two meanings of 'convention'
- Convention object = deprecated/removed in 9
- ExtensionContainer is the successor
- Property.convention() = defaults, alive
- JavaPluginConvention → JavaPluginExtension
basics
~10 sThe old Convention object let plugins mix extra properties/methods into the DSL (convention objects/plugins). It's deprecated and removed in Gradle 9. The replacement is the ExtensionContainer (project.extensions.create) plus lazy Property.convention(...) defaults.
solid answer
~40 sHistorically Gradle had two overlapping mechanisms. The legacy **`Convention`** object (`project.convention`, 'convention plugins'/'convention objects') let plugins inject extra properties and methods into a target object's dynamic DSL — that's how old core plugins (e.g. the Java plugin) exposed `sourceCompatibility` etc. It was untyped, hard to reason about, and is **deprecated, with the `Convention` API removed in Gradle 9**. The modern model splits the concern: **extensions** (registered via `project.extensions.create("name", Type::class.java)`) provide the typed DSL block, and **`Property.convention(...)`** provides *default values* inside those extensions. So the word 'convention' now means two different things: the dead `Convention` object vs. the live `Property.convention()` default mechanism. When migrating, you move custom DSL into an extension class with abstract lazy properties and set defaults with `.convention(...)` instead of registering a convention object.
code
kotlin · 9 lines// Legacy (removed): dynamic, untyped convention object
// project.convention.plugins["java"] ...
// Modern: typed extension + property convention default
abstract class MyToolExtension {
abstract val endpoint: Property<String>
}
val ext = project.extensions.create("mytool", MyToolExtension::class.java)
ext.endpoint.convention("https://default.example")go deeper
Recognize that the old Convention object is legacy and you should use extensions today; deep migration detail not expected.
Clearly separate the two 'convention' meanings and name ExtensionContainer / extensions.create as the replacement.
Explain why dynamic untyped conventions were a problem (typing, Kotlin DSL, config cache) and outline a migration plan.
Discuss governing a large plugin estate's migration off the removed Convention API ahead of a Gradle 9 upgrade.
## Two unrelated things called 'convention' This is a classic interview trap. 'Convention' appears in two places: 1. **The legacy `Convention` object** — a container (`project.convention`, `task.convention`) into which plugins registered *convention objects* that dynamically added properties/methods to the DSL. This predates extensions and the type-safe model. 2. **`Property.convention(...)`** — the modern method that supplies a *default value* for a lazy property. This is alive and idiomatic. ## Why the legacy Convention object was deprecated The `Convention` mechanism relied on dynamic property resolution (Groovy `methodMissing`/`propertyMissing` style), which meant: - No static typing or IDE completion for the injected members. - Ambiguous resolution order between conventions and extensions. - Hard to support from Kotlin DSL and the configuration cache. Gradle introduced the **`ExtensionContainer`** as the typed successor. Plugins now do: ```kotlin val ext = project.extensions.create("mytool", MyToolExtension::class.java) ``` The `Convention` API was deprecated over several releases and **removed in Gradle 9**, so any plugin still calling `project.convention` or implementing `HasConvention` must migrate. ## What the migration looks like Old (convention object) → new (extension + property convention): - Define an `abstract class MyToolExtension` with `abstract val ...: Property<...>` members. - Register it with `extensions.create(...)`. - Provide defaults with `prop.convention(default)` instead of initialising fields on a convention object. - Wire those properties into your tasks (see related leaf on wiring values into tasks). ## Core-plugin example The Java plugin's `sourceCompatibility`/`targetCompatibility` historically lived on a convention object; modern Gradle exposes the typed `java { }` extension (`JavaPluginExtension`) instead. Reading code that still uses `convention.getPlugin(JavaPluginConvention::class.java)` is a tell that it targets old Gradle. ## Interview-ready summary - Legacy `Convention` object = deprecated/removed dynamic DSL injection. - Modern replacement = typed **extensions** for the DSL + **`Property.convention()`** for defaults. - They are *different* features that unfortunately share a word.
- If you see code calling project.convention.getPlugin(JavaPluginConvention::class.java), what does it tell you?It targets an old Gradle version. JavaPluginConvention and the Convention API are deprecated/removed; the modern equivalent is the typed JavaPluginExtension accessed via the java { } extension.
- Does deprecating the Convention object affect Property.convention()?No. They are unrelated features that share the word 'convention'. Property.convention() for defaults is fully supported and idiomatic.
saying these in an interview costs you the question
- Conflating the legacy Convention object with Property.convention() — they are different things.
- Claiming Property.convention() is deprecated.
- Suggesting new plugins should register convention objects.