Your plugin detects a fatal misconfiguration. Walk through how you'd emit an ERROR-severity structured problem AND fail the build, and contrast it with merely reporting it.
answer
- severity = classification, reporter method = outcome
- report() records, continues
- throwing(RuntimeException) records + fails
- still set location/solution/documentedAt on failures
- beats raw GradleException — keeps structure
basics
~10 sSet severity(Severity.ERROR) on the spec, then use reporter.throwing(exception) (passing a RuntimeException) so Gradle records the structured problem and then throws to fail the build. reporter.report(...) would only record it and let the build continue.
solid answer
~50 sSeverity classifies how serious a problem is, but it doesn't control whether the build stops — the **reporter method** does. To both emit a structured ERROR and fail the build, you use `reporter.throwing(...)`: you give it a `RuntimeException` and a problem definition, and Gradle records the structured problem (with its severity, location, solutions, doc link) and then throws that exception, failing the build with a proper structured diagnostic attached. If instead you call `reporter.report(id, spec)`, the problem — even at `Severity.ERROR` — is only recorded; the build continues. So the design decision is: *report* for non-fatal diagnostics (warnings, advice, recoverable errors you want surfaced), and *throwing* when the misconfiguration genuinely cannot proceed. Always still set `severity(ERROR)`, a precise `lineInFileLocation`, a `solution`, and `documentedAt` so the failure that reaches the IDE/console is actionable rather than a bare stack trace.
code
kotlin · 7 lines// Fail with a STRUCTURED problem, not a bare exception:
throw reporter.throwing(RuntimeException("Java 21 required")) { spec ->
spec.severity(Severity.ERROR)
.lineInFileLocation("build.gradle.kts", 4, 1, 20)
.solution("Use JavaLanguageVersion.of(21)")
.documentedAt("https://docs.example.com/jdk")
}go deeper
Recognise that report() doesn't stop the build and something else is needed to fail it.
Correctly pair severity(ERROR) with throwing() to both record and fail, and know report() is record-only.
Give the decision guide (report vs throwing) and argue why structured failure beats a raw exception for IDE/CI consumers.
Set a team convention: fatal paths go through throwing() with full location/solution/docs so every build failure is a navigable, documented diagnostic across console, HTML report, and Tooling API.
## The core distinction: classification vs action A recurring confusion is conflating **severity** (metadata: ADVICE/WARNING/ERROR) with **build outcome** (continue vs fail). They are orthogonal: - **Severity** is set with `spec.severity(Severity.ERROR)` and only *describes* seriousness. - **Build outcome** is decided by *which reporter method* you call. ## reporter.report(...) — record only `problems.getReporter().report(problemId) { spec -> ... }` records the structured problem and returns. The build continues regardless of severity. Use it for warnings, advice, and even non-fatal errors you want visible in the IDE/HTML report without aborting. ## reporter.throwing(...) — record AND fail `throwing` records the structured problem and then throws an exception you supply, halting the build. The exception you pass is a `RuntimeException` (commonly Gradle wraps/associates it with the problem). The shape is along the lines of: ```kotlin val problems = (project as ProjectInternal).services.get(Problems::class.java) val reporter = problems.reporter val problemId = ProblemId.create("bad-jdk", "Unsupported JDK", problemGroup) throw reporter.throwing(RuntimeException("Java 21 is required")) { spec -> spec.severity(Severity.ERROR) .lineInFileLocation("build.gradle.kts", 4, 1, 20) .details("This plugin requires Java 21 but the toolchain resolves to Java 17") .solution("Set java.toolchain.languageVersion = JavaLanguageVersion.of(21)") .documentedAt("https://docs.example.com/jdk-requirement") } ``` `throwing` returns the exception so you can `throw` it (or it throws directly depending on the overload/version — APIs are incubating, so check the exact signature for your Gradle version). The crucial behaviour is: the structured problem is captured *and* the build fails with that problem attached, so the IDE shows a navigable, documented failure rather than scraping a stack trace. ## Decision guide - **Recoverable / informational** → `report` with WARNING or ADVICE. - **A fault but the build can still proceed** → `report` with ERROR (visible, non-fatal). - **Cannot proceed** → `throwing` with a RuntimeException, severity ERROR, full location/solution/docs. ## Why bother vs throw new GradleException Throwing a raw exception loses structure: no severity, no typed location, no solution list, no doc link — the IDE just sees a stack trace. `throwing` gives you a *structured* failure: same fatality, but consumers get an actionable, navigable diagnostic. That's the whole point of routing failures through the Problems API.
- Why prefer reporter.throwing over simply throwing a GradleException?throwing attaches the full structured problem (severity, location, solutions, doc link) to the failure, so IDEs/the HTML report show a navigable, documented diagnostic instead of a bare stack trace.
- When is reporting an ERROR with report() (not throwing) the right call?When the error is real but recoverable — you want it surfaced and grouped as ERROR severity, but the build should still complete (e.g. so the user sees all problems at once).
- Does throwing() guarantee the build fails?Yes — it emits the structured problem and throws the supplied exception, which aborts the build, unlike report() which only records.
saying these in an interview costs you the question
- Saying severity(ERROR) by itself fails the build — only throwing() does.
- Throwing a raw GradleException 'because it's simpler' — you lose all structured diagnostic data.
- Using throwing() for non-fatal warnings, aborting builds that should continue.