How are services like ObjectFactory and ProjectLayout made available to a Plugin<Project>, and why use them instead of Project methods directly?
answer
- @Inject constructor supplies services
- ObjectFactory / ProjectLayout / ProviderFactory
- objects.newInstance for decorated objects
- no live Project at execution (config cache)
- lazy DirectoryProperty over eager File
basics
~10 sGradle injects services into a plugin via an @Inject constructor — e.g. ObjectFactory, ProjectLayout, ProviderFactory. You use them to create Property/Provider instances and resolve paths lazily, which is cleaner and configuration-cache friendly.
solid answer
~40 sGradle can construct your `Plugin<Project>` with a constructor annotated `@Inject` that takes services such as `ObjectFactory`, `ProviderFactory`, `ProjectLayout`, or `ExecOperations`. Gradle's dependency-injection container supplies them. You prefer these over reaching through `project` because: (1) `ObjectFactory.property(...)`/`listProperty(...)`/`newInstance(...)` create managed lazy values and decorated objects; (2) `ProjectLayout.buildDirectory`/`projectDirectory` give lazy `DirectoryProperty` paths instead of eager `File`s; (3) injected services are the building blocks of **configuration-cache compatibility** — holding a `Provider` is safe to serialize whereas holding the live `Project` is not. The same injection works in tasks and extensions (abstract `@Inject` getters / managed properties). Inside `apply` you can also call `project.objects`, `project.layout`, `project.providers` — same services — but constructor injection is the testable, idiomatic form for non-trivial plugins.
code
kotlin · 11 linesclass ReportPlugin @Inject constructor(
private val objects: ObjectFactory,
private val layout: ProjectLayout,
) : Plugin<Project> {
override fun apply(project: Project) {
project.tasks.register("report", ReportTask::class.java) { task ->
// lazy build-dir location, config-cache safe
task.output.set(layout.buildDirectory.file("reports/out.txt"))
}
}
}go deeper
Aware that project.objects/layout/providers exist for creating Property/path values.
Use @Inject constructor for ObjectFactory/ProjectLayout and know what each provides.
Explain why injection plus lazy Providers underpins configuration-cache compatibility and avoids live Project capture.
Mandate injected-service patterns so the org's plugins are configuration-cache compatible and testable by construction.
## Gradle's service injection Gradle instantiates plugin, task, and extension types through its own object-creation mechanism, which supports **dependency injection** of a fixed set of build services. You opt in with an `@Inject`-annotated constructor: ```kotlin import javax.inject.Inject class MyPlugin @Inject constructor( private val objects: ObjectFactory, private val layout: ProjectLayout, private val providers: ProviderFactory, ) : Plugin<Project> { override fun apply(project: Project) { /* use objects/layout/providers */ } } ``` Commonly injectable services include `ObjectFactory`, `ProviderFactory`, `ProjectLayout`, `ExecOperations`, `FileSystemOperations`, and `ArchiveOperations`. Gradle resolves them automatically; you never `new` them. ## The services and what they're for - **ObjectFactory** — factory for lazy/managed objects: `objects.property(String::class.java)`, `objects.listProperty(...)`, `objects.newInstance(SomeType::class.java)` (creates a decorated instance with its own injected services and managed properties). - **ProviderFactory** — `providers.provider { ... }`, `providers.environmentVariable("X")`, `providers.gradleProperty("y")` — lazy sources of values. - **ProjectLayout** — `layout.buildDirectory` (a `DirectoryProperty`), `layout.projectDirectory`; lazy filesystem locations instead of eager `File`. - **ExecOperations / FileSystemOperations** — run external processes / copy files from inside tasks without the deprecated `Project.exec`/`Project.copy`. ## Why not just use Project? Inside `apply` you *can* write `project.objects`, `project.layout`, `project.providers` — these expose the same services, and for a tiny plugin that's fine. But constructor injection is preferred because: 1. **Testability** — you can construct the plugin (or use ProjectBuilder) and the dependencies are explicit. 2. **Configuration cache** — the configuration cache serializes task state between runs and **forbids holding a live `Project` reference at execution time**. If a task captured `project`, it breaks. Instead, capture `Provider`/`Property` values produced via `ObjectFactory`/`ProjectLayout`, which serialize cleanly. Service injection is how you get those without smuggling in `Project`. 3. **Laziness** — `layout.buildDirectory.file("out.txt")` is a `Provider<RegularFile>` resolved late, vs `project.buildDir` returning an eager `File`. ## Same pattern in tasks and extensions Tasks declare injected services with `@get:Inject abstract val objects: ObjectFactory` (or constructor `@Inject`), and abstract `Property` getters become managed. This consistency is why a plugin built around injected services composes cleanly with configuration-cache-safe tasks.
- Why is holding a Project reference in a task a configuration-cache problem?The configuration cache serializes task state across runs; a live Project isn't serializable and represents build-time-only state. You must capture Providers/Properties (from ObjectFactory/ProjectLayout) instead, which serialize cleanly.
- What does objects.newInstance(Type::class.java) give you over Type()?A Gradle-decorated instance: managed Property getters are implemented, and the type can itself receive injected services — impossible with a plain constructor call.
- Can you get these services without a constructor at all?Yes — inside apply use project.objects/project.layout/project.providers, or in tasks declare abstract @get:Inject getters. Constructor injection is just the most explicit/testable form.
saying these in an interview costs you the question
- Claiming you must instantiate ObjectFactory yourself.
- Capturing project (or project.buildDir as File) into task state, breaking configuration cache.
- Thinking service injection is unrelated to laziness/config-cache.