skip to content

What are ProblemId and ProblemGroup, and why does giving a problem a stable identity matter?

level: middleimportance: must knowfreq 28%

answer

  1. ProblemGroup = namespace (nestable)
  2. ProblemId = name (key) + displayName
  3. stable id enables dedup/aggregation/quick-fix
  4. per-occurrence detail goes in contextualLabel/details/location
  5. anti-pattern: file/line embedded in id

basics

~20 s

A ProblemGroup is a namespace (e.g. your plugin's category) and a ProblemId is a stable identity within it — a name plus human displayName. The stable ID lets Gradle and IDEs group, count, and deduplicate the same kind of problem.

solid answer

~50 s

Every structured problem must declare an identity so consumers can categorize it. A **`ProblemGroup`** is a hierarchical namespace — created with `ProblemGroup.create(name, displayName)` and optionally nested under a parent group — that bundles related problems (e.g. all of one plugin's checks). A **`ProblemId`** is the leaf identity, `ProblemId.create(name, displayName, group)`: `name` is the **stable machine key** and `displayName` is the human label. The identity matters because the same logical problem can fire thousands of times: a stable ID lets the console summary say "312 occurrences of *deprecated-config*", lets build scans aggregate, lets IDEs map a problem class to a quick-fix, and lets dashboards trend issues over time. If you generated a fresh ID per occurrence (e.g. embedding a filename in the `name`), nothing could be grouped. So: keep the *identity* stable and category-level; put the per-occurrence specifics in `contextualLabel`, `details`, and the location — not in the ID.

code

kotlin · 11 lines
kotlin
val group = ProblemGroup.create("deps", "Dependency checks")
val id = ProblemId.create("version-conflict", "Version conflict", group)

// Same id reused for every occurrence -> Gradle can group/count them
conflicts.forEach { c ->
    problems.reporter.report(id) { spec ->
        spec.contextualLabel("Conflict on ${c.module}: ${c.requested} vs ${c.selected}")
            .details("Requested ${c.requested} but ${c.selected} was selected.")
            .severity(Severity.WARNING)
    }
}

go deeper

for a junior

Know that ProblemGroup is a namespace and ProblemId is the problem's name plus display name.

for a middle

Explain why stable ids enable dedup/aggregation and that variable data belongs in contextualLabel/details/location.

for a senior

Design a coherent group hierarchy and a naming convention so ids are stable and discoverable.

for a principal

Treat the id taxonomy as an org contract consumed by dashboards/IDEs; govern naming and avoid breaking ids across releases.

## Two-level identity The Problems API models identity as a **group + id** pair: - **`ProblemGroup`** — a namespace. Create with `ProblemGroup.create("my-plugin", "My Plugin")`. Groups can nest: `ProblemGroup.create("naming", "Naming rules", parentGroup)`, forming a hierarchy like `my-plugin > naming`. - **`ProblemId`** — the concrete kind of problem inside a group: `ProblemId.create("class-not-capitalized", "Class name not capitalized", group)`. The first arg is a **stable key**; the second is the human display name. ```kotlin val plugin = ProblemGroup.create("my-plugin", "My Plugin") val naming = ProblemGroup.create("naming", "Naming", plugin) val id = ProblemId.create("class-case", "Class name not capitalized", naming) ``` ## Why stability is the whole point A structured problem is only useful to downstream tools if **two occurrences of the same kind share the same identity**. With a stable `ProblemId`: - The **console** prints a deduplicated count instead of N identical lines. - **Build scans** aggregate: "deprecated-config fired 412 times across 3 projects." - **IDEs** can map the `ProblemId` to an inline annotation style or a quick-fix. - **CI dashboards** can trend a specific problem id release over release. ## The classic anti-pattern Encoding per-occurrence data into the id: ```kotlin // WRONG: identity changes every time -> nothing groups ProblemId.create("bad-name-in-${file.name}", "...", group) ``` The id must be the **category**; the **variable** part (which file, which line, the exact message) belongs in: - `contextualLabel("...")` — the situation-specific headline, - `details("...")` — longer text, - the **location** (file + line), none of which affect grouping. ## Mental model Think of `ProblemGroup`/`ProblemId` like a **logging category + event code**, and `contextualLabel`/`details`/location like the **per-event message arguments**. The code stays fixed; the arguments vary.

  • Should the offending file path go into the ProblemId name?
    No. That would make the id unique per occurrence and break grouping. Put the file in the location, and the specifics in contextualLabel/details; keep the id at category level.
  • Can ProblemGroups be nested?
    Yes. ProblemGroup.create(name, displayName, parentGroup) builds a hierarchy, e.g. my-plugin > naming, which consumers can use to organize problems.

ProblemId is the error CODE (E1042); contextualLabel/details are the per-incident message and where it happened. You don't mint a new code for every incident.

saying these in an interview costs you the question

  • Embedding per-occurrence data (file, line, value) into the ProblemId name.
  • Creating a brand-new ProblemId object for every report call instead of reusing the category id.

context