Compare ObjectFactory and ProviderFactory: what does each create, and why do you inject them instead of using project.objects / project.providers?
answer
- ObjectFactory → property/listProperty/newInstance
- ProviderFactory → environmentVariable/exec/provider
- providers tracks reads as inputs
- raw System.getenv is invisible to cache
- container vs value
basics
~20 sObjectFactory builds managed objects and lazy properties (Property, ListProperty, instances of @Inject types). ProviderFactory builds and reads Providers — env vars, system/Gradle properties, and config-time exec. You inject both so tasks don't depend on the Project at execution time.
solid answer
~40 s`ObjectFactory` is a **factory for managed values and objects**: `objects.property(String::class.java)`, `objects.listProperty(...)`, `objects.fileCollection()`, `objects.directoryProperty()`, `objects.domainObjectSet(...)`, and `objects.newInstance(MyType::class.java)` for types that themselves use `@Inject`. `ProviderFactory` is the **factory for and reader of lazy Providers**: `providers.provider { }`, `providers.environmentVariable("X")`, `providers.systemProperty("y")`, `providers.gradleProperty("z")`, and `providers.exec { }` / `providers.of(ValueSource...)`. Both are injected with `@get:Inject abstract val objects: ObjectFactory` / `providers: ProviderFactory`. The reason to inject rather than use `project.objects` / `project.providers` is the same as for exec/copy: at execution time the configuration cache makes `Project` unavailable, and even at configuration time injecting keeps the type decoupled and unit-testable. ProviderFactory's env/property readers are especially important because they capture the read as a tracked input, so the configuration cache invalidates correctly when the value changes — reading `System.getenv` directly would not.
code
kotlin · 11 linesabstract class BuildInfo : DefaultTask() {
@get:Inject abstract val objects: ObjectFactory
@get:Inject abstract val providers: ProviderFactory
@get:Input val branch: Property<String> = objects.property(String::class.java)
init {
// lazy, cache-tracked
branch.convention(providers.environmentVariable("GIT_BRANCH").orElse("local"))
}
}go deeper
Know ObjectFactory makes properties and ProviderFactory makes/reads providers.
Give concrete factory methods for each and pair them (Property from objects, value from providers).
Explain config-cache input tracking via ProviderFactory and why raw System.getenv is unsafe; tie injection to decoupling.
Standardize lazy/provider-based plugin APIs and ban direct env/system reads in build code via review and tooling.
## Two different jobs ### ObjectFactory — make managed things `ObjectFactory` constructs Gradle-managed objects and the lazy property types that form the modern DSL: - `property(Class)` → `Property<T>` - `listProperty` / `setProperty` / `mapProperty` → lazy collections - `fileCollection()`, `directoryProperty()`, `fileProperty()` - `domainObjectSet`, `namedDomainObjectSet` - `newInstance(Type, ...args)` → an instance of a type that *itself* uses `@Inject` (this is how custom extensions get their own services) ```kotlin abstract class Ext @Inject constructor(objects: ObjectFactory) { val mode: Property<String> = objects.property(String::class.java) } ``` ### ProviderFactory — make and read lazy values `ProviderFactory` produces `Provider`s and reads external state lazily: - `provider { compute() }` → deferred computation - `environmentVariable("CI")`, `systemProperty("x")`, `gradleProperty("version")` → `Provider<String>` that Gradle tracks as a **configuration input** - `exec { }` → run a process at configuration time, cache-safely - `of(ValueSourceType, config)` → custom value sources ```kotlin val isCi = providers.environmentVariable("CI").map { it == "true" } ``` ## Why inject them 1. **Configuration cache:** `project.objects` / `project.providers` read through `Project`, which is gone at execution time. Injected `ObjectFactory`/`ProviderFactory` are provided directly. 2. **Tracked inputs:** `providers.environmentVariable` records the read so the cache invalidates when the env changes; a raw `System.getenv()` is invisible to Gradle and produces stale-cache bugs. 3. **Decoupling / testing:** a task that declares `@get:Inject abstract val objects` can be exercised without a real `Project`. ## How they relate They're complementary: `ObjectFactory` creates the *container* (`Property<String>`), `ProviderFactory` often supplies the *value* you wire into it: ```kotlin myTask.message.set(providers.environmentVariable("MSG")) ``` Here `message` is a `Property<String>` made by ObjectFactory, fed a `Provider<String>` made by ProviderFactory — lazy end to end. ## Mental model ObjectFactory = "give me a managed slot/object." ProviderFactory = "give me a lazy, tracked value (often from the environment)." Inject both; never read them off `project` inside execution-time code.
- Why read an environment variable via providers.environmentVariable instead of System.getenv?providers.environmentVariable returns a Provider that Gradle records as a configuration input, so the configuration cache invalidates when the variable changes. System.getenv is opaque to Gradle and can leave you with a stale cached configuration.
- How does ObjectFactory help a custom extension get its own injected services?objects.newInstance(MyExt::class.java) constructs the type through Gradle's instantiator, so any @Inject constructor parameters or getters on MyExt are themselves filled in — letting extensions hold their own ObjectFactory/ProviderFactory.
saying these in an interview costs you the question
- Saying ObjectFactory and ProviderFactory are interchangeable.
- Reading System.getenv()/System.getProperty() for build inputs instead of ProviderFactory.
- Using project.objects inside a task action under the configuration cache.