skip to content

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.

level: seniorimportance: should knowfreq 18%

answer

  1. severity = classification, reporter method = outcome
  2. report() records, continues
  3. throwing(RuntimeException) records + fails
  4. still set location/solution/documentedAt on failures
  5. beats raw GradleException — keeps structure

basics

~10 s

Set 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 s

Severity 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
kotlin
// 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

for a junior

Recognise that report() doesn't stop the build and something else is needed to fail it.

for a middle

Correctly pair severity(ERROR) with throwing() to both record and fail, and know report() is record-only.

for a senior

Give the decision guide (report vs throwing) and argue why structured failure beats a raw exception for IDE/CI consumers.

for a principal

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.

context