When building a nested extension, why do you create the nested object with ObjectFactory.newInstance and declare its fields as abstract Property getters instead of plain fields with a constructor?
answer
- managed object = abstract + Property getters
- newInstance implements getters + injects services
- keeps values lazy / wirable
- Configuration Cache safe
- recurses into nested managed types
basics
~10 sObjectFactory.newInstance makes Gradle create a managed object: it injects services and materializes the abstract Property getters for you, keeping values lazy and wirable into tasks. A plain constructor gives none of that.
solid answer
~40 sNested extension types should be `abstract` with abstract `Property<T>`/`ListProperty<T>`/`RegularFileProperty` getters. When you create them via `objectFactory.newInstance(Type::class.java)`, Gradle generates a concrete subclass at runtime that implements those getters with real managed `Property` instances, performs constructor `@Inject` for services like `ObjectFactory`/`ProjectLayout`, and decorates the object so nested action methods and convention defaults work. This keeps configuration *lazy* — values are providers resolved at execution time — so they can be wired into tasks and participate in `@Nested` fingerprinting. A `new`-ed object skips decoration: the abstract getters are unimplemented (it won't even instantiate), and you'd be forced into eager, mutable plain fields that break lazy wiring and Configuration Cache compatibility.
code
kotlin · 13 linesabstract class ServerSpec {
abstract val host: Property<String>
abstract val port: Property<Int>
}
// Created by Gradle, getters implemented, fields lazy:
val spec = objects.newInstance(ServerSpec::class.java)
spec.host.set("db.local") // lazy Property, set now or wired from elsewhere
// Wire into a task lazily
tasks.register<PackageTask>("pack") {
serverHost.set(spec.host) // resolved at execution time
}go deeper
Know you should use objects.newInstance, not a constructor, to create extension/spec objects.
Explain that managed abstract Property getters are auto-implemented and kept lazy and wirable.
Tie laziness to task wiring and Configuration Cache; reason about recursive managed creation.
Mandate the managed-object pattern across plugins so all DSLs are lazy and Configuration-Cache compatible by default.
## Managed objects, in brief Gradle has a *managed object* model: you declare a type as `abstract` (or an interface) with abstract getters of *managed* types — chiefly `Property<T>`, `ListProperty<T>`, `SetProperty<T>`, `MapProperty<K,V>`, `RegularFileProperty`, `DirectoryProperty`, and nested managed beans. Gradle generates the implementation at runtime. The factory for this is `org.gradle.api.model.ObjectFactory`, obtained by `@Inject` or `project.objects`. ## What newInstance does that `new` cannot `objects.newInstance(ServerSpec::class.java, extraCtorArgs...)`: 1. **Implements abstract managed getters** — each `abstract val host: Property<String>` is backed by a freshly created `Property` (via `objects.property(...)`). You never write `objects.property` by hand for these. 2. **Injects services** — constructor parameters annotated/injected such as `ObjectFactory`, `ProjectLayout`, `ProviderFactory` are supplied automatically. 3. **Decorates the type** — enables Gradle DSL features: single-`Action` methods become `name { }` blocks, `Property.convention(...)` defaults work, and the object is Configuration-Cache friendly. A plain `new ServerSpec()` against an abstract class won't even compile/instantiate, and a non-abstract hand-written version forces eager mutable fields that lose laziness. ## Why laziness matters for nesting Nested extension values are usually *wired into tasks*. If a field is a `Property<String>`, a task can do `task.host.set(extension.server.host)` and the value is resolved lazily at execution, after all configuration has run. Eager `String` fields would capture whatever value existed at wiring time. Lazy properties also play correctly with the **Configuration Cache**, which serializes the task graph — managed providers are serializable, arbitrary mutable objects may not be. ## Recursive nesting `newInstance` recurses: if a managed type has an abstract getter returning *another* managed type, Gradle creates that too. With abstract extensions you often don't call `newInstance` yourself at all — `extensions.create("my", MyExtension::class.java)` creates the whole managed tree. ```kotlin abstract class ServerSpec { abstract val host: Property<String> abstract val port: Property<Int> } abstract class MyExtension { abstract val server: ServerSpec // created recursively by Gradle } // extensions.create("my", MyExtension::class.java) builds the whole tree ``` ## Summary Managed + ObjectFactory = no boilerplate, lazy values, working DSL features, and Configuration-Cache safety. Plain constructors break all four.
- What managed types can abstract getters use?Property<T>, ListProperty<T>, SetProperty<T>, MapProperty<K,V>, RegularFileProperty, DirectoryProperty, NamedDomainObjectContainer, and other managed/nested beans. Gradle materializes each.
- How does using managed Property fields help the Configuration Cache?Managed providers are serializable and capture references lazily, so the configured state can be stored and reloaded without re-running configuration. Arbitrary mutable objects may not serialize cleanly.
- Do you ever call newInstance yourself for an extension created via extensions.create?Usually not — extensions.create already uses ObjectFactory and recurses into nested managed types. You call newInstance explicitly when constructing standalone nested beans not registered as extensions.
saying these in an interview costs you the question
- Manually doing `objects.property(String::class.java)` for every field of an abstract managed type — unnecessary; Gradle materializes abstract Property getters.
- Using eager String/Int fields with a constructor, which breaks lazy wiring and Configuration Cache.