How does ObjectFactory.newInstance() work, and how does service injection into the created instance behave?
answer
- newInstance = generate impl + inject services
- @Inject params from registry, rest positional args
- nested DSL blocks via newInstance
- ObjectFactory/ProjectLayout/WorkerExecutor injectable
- decorated, not plain new
basics
~10 sobjects.newInstance(Type, args) creates an instance, generating impls for abstract managed types and supplying constructor @Inject services (like ObjectFactory) plus any extra args you pass. It's how you create nested DSL objects.
solid answer
~50 s`ObjectFactory.newInstance(Class<T>, Object... args)` is Gradle's decorated instantiator. It (1) generates the runtime implementation for **managed types** (abstract getters become initialized properties), and (2) performs **dependency injection** of Gradle services into the constructor. Constructor parameters fall into two groups: services Gradle knows how to inject — annotate them `@Inject` (`ObjectFactory`, `ProjectLayout`, `ProviderFactory`, `WorkerExecutor`, `ExecOperations`, etc.) — and your own values, which you pass positionally as the trailing `args`. Gradle matches the args to the non-injected parameters in order. You use `newInstance()` to build **nested DSL objects** inside extensions (e.g. an extension exposing a `tls { ... }` block backed by a managed `TlsOptions` you create with `objects.newInstance(TlsOptions::class.java)`), or to create helper model objects on demand. The created object is *decorated*: it gets the generated managed-property storage and can itself receive injected services through its own constructor. This is the same mechanism Gradle uses internally to instantiate tasks and extensions.
code
kotlin · 10 linesabstract class Repo @Inject constructor(
objects: ObjectFactory,
val name: String
) {
abstract val url: Property<String>
}
// name -> "central"; url initialized by Gradle
val repo = project.objects.newInstance(Repo::class.java, "central")
repo.url.set("https://repo.example.com")go deeper
Know newInstance() creates managed objects and you pass extra constructor args after the type.
Use it to build nested DSL blocks and pass user args alongside @Inject services.
Explain decoration, the inject-vs-arg split, and which services are injectable in which scope.
Design plugin object graphs around newInstance/managed types for consistency and configuration-cache safety across the org.
## What newInstance does `objects.newInstance(MyType::class.java, extraArg1, extraArg2)` asks Gradle's **instantiator** to create a *decorated* instance of `MyType`. Decoration means two things happen that a plain `new` would not: 1. **Managed-type generation.** If `MyType` is abstract with abstract getters returning model types, Gradle generates the concrete subclass and initializes every managed property (`Property`, `RegularFileProperty`, nested managed types, …). 2. **Service injection.** Constructor parameters annotated `@Inject` are filled from Gradle's service registry. ## Constructor parameter rules Gradle splits the constructor parameter list: - Parameters resolvable as **services** must be marked `@Inject`. Injectable services include `ObjectFactory`, `ProjectLayout`, `ProviderFactory`, `WorkerExecutor`, `ExecOperations`, `FileSystemOperations`, `ArchiveOperations`, and `ToolingModelBuilderRegistry` (context-dependent). - The remaining parameters are **user-supplied** and matched, in order, to the trailing `args` you pass to `newInstance`. ```kotlin abstract class Endpoint @Inject constructor( objects: ObjectFactory, // injected service val id: String // user-supplied arg ) { abstract val url: Property<String> // managed property, auto-initialized } val ep = objects.newInstance(Endpoint::class.java, "primary") // id == "primary"; url is a ready Property<String> ``` Note: the `@Inject`-annotated constructor signals which constructor Gradle should use; services come from the registry, and `"primary"` maps to `id`. ## Building nested DSL blocks The classic use is a configurable nested block in an extension: ```kotlin abstract class ServerExtension @Inject constructor( private val objects: ObjectFactory ) { val tls: TlsOptions = objects.newInstance(TlsOptions::class.java) fun tls(action: Action<in TlsOptions>) = action.execute(tls) } abstract class TlsOptions { abstract val enabled: Property<Boolean> } ``` Now build scripts can write `server { tls { enabled.set(true) } }`. (For collections of named nested objects you'd use a `NamedDomainObjectContainer` created via `objects.domainObjectContainer(...)` — but a single nested block is just `newInstance`.) ## How it differs from plain instantiation - `new Endpoint(...)` → no managed-property generation, no service injection; abstract getters would be unimplemented. - `objects.newInstance(...)` → fully decorated, ready to use, configuration-cache friendly. ## Pitfalls - Passing the wrong number/order of trailing args throws an instantiation error — args map positionally to non-injected params. - Don't mix `@Inject` on some constructors and not others ambiguously; mark the intended constructor. - Services available depend on context (project vs settings vs task); injecting an unsupported service fails at creation time.
- How are constructor arguments matched between injected services and user values?Parameters annotated @Inject are resolved from Gradle's service registry; the remaining parameters are filled, in declaration order, by the trailing args you pass to newInstance().
- Why use newInstance instead of `new` for a nested options object?new won't generate the managed-property implementation or inject services, so abstract getters stay unimplemented and the object isn't decorated/CC-friendly. newInstance gives a fully wired instance.
- Name a few services injectable via @Inject into a newInstance-created type.ObjectFactory, ProjectLayout, ProviderFactory, WorkerExecutor, ExecOperations, FileSystemOperations, ArchiveOperations — availability depends on the context (project/task/settings).
saying these in an interview costs you the question
- Using `new`/constructor for managed types, leaving abstract getters unimplemented.
- Forgetting that user args map positionally to non-@Inject constructor params.
- Assuming any service is injectable in any context (it depends on scope).