How do you obtain the Problems service inside a plugin or task, and why can't you just instantiate it?
answer
- @Inject constructor
- abstract class plugin/task
- service registry, internal impl
- problems.reporter / getReporter()
- Gradle subclasses/instruments managed types
basics
~10 sYou don't create it — Gradle injects it. Add a constructor parameter of type Problems annotated with @Inject on your Plugin or Task (using an abstract class), and Gradle's service registry supplies the instance.
solid answer
~40 s`Problems` is a Gradle-managed **build service** living in the service registry, so it must be **injected**, not constructed. The idiomatic way is constructor injection: declare an `abstract class` plugin or task with `@Inject constructor(private val problems: Problems)`. Gradle instantiates the class and wires the dependency. The class must be `abstract` (or have an injectable constructor) so Gradle can subclass/instrument it. From the service you call `problems.getReporter()` (Kotlin: `problems.reporter`) to get the `ProblemReporter`, then `report(id) { ... }`. You can't `Problems()` it because the implementation is internal, stateful, and tied to the current build's reporting infrastructure (it routes to the console, HTML report, and Tooling API listeners). Injection also works for ad-hoc task actions via `project.objects` / `ObjectFactory`-managed types, but constructor `@Inject` on the type itself is the standard pattern.
code
kotlin · 14 linesabstract class SchemaCheckTask @Inject constructor(
private val problems: Problems
) : DefaultTask() {
@TaskAction
fun check() {
val group = ProblemGroup.create("schema", "Schema validation")
val id = ProblemId.create("missing-id", "Missing primary key", group)
problems.reporter.report(id) { spec ->
spec.contextualLabel("Table 'orders' has no primary key")
.severity(Severity.WARNING)
}
}
}go deeper
Know it's @Inject'd via the constructor and you don't create it.
Explain abstract-class instrumentation, the service registry, and getReporter().
Contrast constructor injection with ObjectFactory-based injection and explain the per-build statefulness reason.
Discuss encapsulating injection in a shared base plugin so all org plugins report consistently.
## Services are injected, not created Gradle exposes much of its functionality as **services** held in an internal **service registry**: `ObjectFactory`, `ProjectLayout`, `ExecOperations`, `FileSystemOperations`, and — for diagnostics — `Problems`. These are implemented by internal classes you can't and shouldn't instantiate. Instead you *declare a dependency* and Gradle injects it. ## Constructor injection (the standard pattern) Declare your plugin or task as an **`abstract class`** with an `@Inject` constructor: ```kotlin import org.gradle.api.problems.Problems import javax.inject.Inject abstract class ValidatePlugin @Inject constructor( private val problems: Problems ) : Plugin<Project> { /* ... */ } ``` Why `abstract`? Gradle **subclasses and instruments** managed types at runtime (to support lazy properties, decorated getters, etc.). An abstract class signals "Gradle, please materialize me," and the `@Inject` constructor tells the injector which services to supply. ## In a Task The same applies to tasks: ```kotlin abstract class CheckTask @Inject constructor( private val problems: Problems ) : DefaultTask() { @TaskAction fun run() { problems.reporter.report(/* ... */) { /* ... */ } } } ``` ## Getting the reporter `Problems` is a thin entry point. The actual reporting object is the **`ProblemReporter`**, obtained via `getReporter()` (Kotlin property `reporter`). That reporter has `report(...)` and `throwing(...)`. ## Why not `new`? Three reasons: 1. The implementation is **internal** (`org.gradle.api.problems.internal`). 2. It is **stateful per build** — it must route problems to the right listeners (console summary, HTML report writer, Tooling API progress events). 3. Constructing your own would emit problems into the void, disconnected from Gradle's infrastructure. ## Common mistake Making the plugin a non-abstract class with a hand-written constructor and trying to pass `Problems` yourself — there's nothing valid to pass. Let Gradle do it.
- Why must the plugin/task class be abstract?Gradle subclasses and instruments managed types at runtime to support injection and lazy/decorated properties. An abstract class lets Gradle materialize the concrete instance and wire the @Inject constructor.
- What's the difference between Problems and ProblemReporter?`Problems` is the injected entry-point service; `getReporter()` returns the `ProblemReporter`, which actually carries `report(...)` and `throwing(...)`.
saying these in an interview costs you the question
- Saying you call `new Problems()` or pass it manually.
- Forgetting the class must be abstract / have an @Inject constructor.