What is Gradle's Problems API and why would a plugin author report a structured problem instead of just throwing an exception or logging a warning?
answer
- structured vs free-text logging
- many consumers: console, HTML, IDE, scan
- ProblemId + contextualLabel + details + severity
- injected Problems service
- report() vs throwing()
basics
~20 sThe Problems API lets a plugin report build issues as structured data (an ID, a message, severity, location) instead of plain text. Gradle then surfaces them uniformly in the console, the HTML report, IDEs, and the Tooling API.
solid answer
~40 sIntroduced as an incubating service, the **Problems API** is Gradle's structured channel for emitting diagnostics. Instead of a free-text `logger.warn(...)` or a raw exception, a plugin injects the `Problems` service and calls `problems.getReporter().report(id) { ... }`, attaching a stable `ProblemId`, a `contextualLabel`, `details`, severity, location, and suggested solutions. The value is that the same problem is then consumed by **multiple front-ends**: the console summary, the generated HTML problems report, IDEs via the **Tooling API** (so they can show inline annotations), and build scans. Logging is unstructured and lost across consumers; exceptions abort the build and carry no machine-readable category. Structured problems are categorizable, deduplicable, and actionable, and you can report them *without* failing the build (`report`) or *while* failing it (`throwing`).
code
kotlin · 13 linesabstract class LintPlugin @Inject constructor(
private val problems: Problems
) : Plugin<Project> {
override fun apply(project: Project) {
val group = ProblemGroup.create("lint", "Lint checks")
val id = ProblemId.create("naming", "Bad naming", group)
problems.reporter.report(id) { spec ->
spec.contextualLabel("Class 'foo' should be capitalized")
.details("Type names must start with an uppercase letter.")
.severity(Severity.WARNING)
}
}
}go deeper
Know it exists, that it's structured rather than free text, and that you inject the Problems service and call report().
Explain the multi-consumer fan-out (console/HTML/IDE/scan) and the report vs throwing distinction.
Discuss when to migrate existing warnings to structured problems and how stable IDs enable dedup/aggregation.
Frame it as the org-wide diagnostics contract: stable taxonomy of ProblemGroups across plugins so dashboards and IDEs stay consistent.
## The problem with logging and exceptions For years a Gradle plugin had two ways to tell a user something was wrong: - `logger.warn("...")` / `logger.error("...")` — free text. It scrolls past in the console, is invisible to IDEs, and has no identity you can group or count. - `throw GradleException("...")` — aborts the build, but again is just a string with a stack trace. Neither is *machine-readable*. An IDE can't turn a warning into an inline annotation; a build scan can't say "this same deprecation fired 412 times"; a CI dashboard can't categorize failures. ## What the Problems API is The **Problems API** (package `org.gradle.api.problems`, incubating in Gradle 8.x) is a structured reporting service. A problem carries: - a **`ProblemId`** — a stable identity (a `name` + human `displayName`) that lives inside a **`ProblemGroup`** (a namespace, e.g. your plugin), - a **`contextualLabel`** — the short, situation-specific headline, - **`details`** — longer explanatory text, - a **severity** (`ADVICE` / `WARNING` / `ERROR`), - optionally a **location** (file/line, or a task path), - optionally **solutions** and a **documentation link**. ## Why structured Because one report fans out to **many consumers**: 1. The **console** prints a deduplicated summary. 2. Gradle writes an **HTML problems report** under the build dir. 3. **IDEs** receive problems over the **Tooling API** and render inline annotations. 4. **Build scans** aggregate and categorize them. ## How you get the service You cannot `new` it — Gradle injects it. In a plugin or task you use constructor injection with `@Inject`: ```kotlin abstract class MyPlugin @Inject constructor( private val problems: Problems ) : Plugin<Project> ``` Then `problems.getReporter().report(id) { spec -> ... }`. ## Report vs. throw - `reporter.report(id) { ... }` records the problem **without** failing the build — perfect for warnings and advice. - `reporter.throwing(exception, id) { ... }` records it **and** throws, so it both fails the build *and* shows up structured. The key mental model: **logging is for humans reading a terminal; the Problems API is for every tool that consumes the build.**
- Does reporting a problem fail the build?No. `reporter.report(...)` only records it. To also fail, use `reporter.throwing(exception, id) { ... }`, which records the structured problem and throws.
- Who consumes a reported problem besides the console?The generated HTML problems report, IDEs via the Tooling API (as inline annotations), and build scans, which can aggregate and categorize them.
Logging is a sticky note shouted into one room; a structured problem is a filed ticket with an ID and category that every department (IDE, console, scan) can read.
saying these in an interview costs you the question
- Saying the Problems API replaces exceptions for control flow — it's about diagnostics, not flow.
- Claiming report() fails the build.