skip to content

How do you obtain the Problems service inside a plugin or task, and why can't you just instantiate it?

level: middleimportance: must knowfreq 30%

answer

  1. @Inject constructor
  2. abstract class plugin/task
  3. service registry, internal impl
  4. problems.reporter / getReporter()
  5. Gradle subclasses/instruments managed types

basics

~10 s

You 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 lines
kotlin
abstract 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

for a junior

Know it's @Inject'd via the constructor and you don't create it.

for a middle

Explain abstract-class instrumentation, the service registry, and getReporter().

for a senior

Contrast constructor injection with ObjectFactory-based injection and explain the per-build statefulness reason.

for a principal

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.

context