Contrast the typed events-package ProgressListener with the legacy org.gradle.tooling.ProgressListener. When would you use each?
answer
- two ProgressListener interfaces, different packages
- legacy = getDescription() string only
- events package = StartEvent/FinishEvent + descriptor + result
- structured filters by OperationType
- IDEs use the structured one
basics
~10 sThe legacy listener gives only a coarse string status via statusChanged(ProgressEvent.getDescription()). The newer events-package listener delivers structured, typed events (StartEvent/FinishEvent, per-operation descriptors and results) you can filter by OperationType.
solid answer
~40 sThere are two `ProgressListener` interfaces. The **legacy** one, `org.gradle.tooling.ProgressListener`, has a single `statusChanged(ProgressEvent)` where that `ProgressEvent` only exposes `getDescription()` — a human-readable string like "Building" or "Configuring". It carries no structure, no operation identity, and no outcome, so you can show a status line but can't build a tree or test runner from it. The **structured** API in `org.gradle.tooling.events` replaces it: `addProgressListener(ProgressListener, OperationType...)` delivers a rich `ProgressEvent` hierarchy — `StartEvent`/`FinishEvent` with typed descriptors (`TaskOperationDescriptor`, `JvmTestOperationDescriptor`) and `OperationResult`s (success/failure/skipped/up-to-date/from-cache). You subscribe per `OperationType`. Use the structured API for anything that renders build/test structure (i.e. every modern IDE integration); the legacy listener survives only for simple status text or for talking to very old daemons. They're distinct overloads of `addProgressListener`, so picking the structured one is just a matter of importing the `events` package types.
code
kotlin · 12 lines// Legacy: coarse string only
launcher.addProgressListener(
org.gradle.tooling.ProgressListener { e -> statusLabel.text = e.description }
)
// Structured: typed, filterable
launcher.addProgressListener(
org.gradle.tooling.events.ProgressListener { e ->
if (e is FinishEvent) tree.complete(e.descriptor, e.result)
},
OperationType.TASK
)go deeper
Know there's a newer structured listener and an old string-only one; prefer the structured one.
Name both packages, contrast getDescription() vs the typed event hierarchy, and say which IDEs use.
Discuss descriptor/result richness, OperationType filtering, and the import-ambiguity gotcha.
Reason about backward-compatibility rationale for keeping the legacy interface and migration strategy for tools.
## Two listeners, same method name Gradle's Tooling API confusingly ships two interfaces both called `ProgressListener`, in different packages: - **Legacy:** `org.gradle.tooling.ProgressListener` - **Structured:** `org.gradle.tooling.events.ProgressListener` Both are registered via overloads of `addProgressListener` on a `LongRunningOperation` (the base of `BuildLauncher`, `ModelBuilder`, `TestLauncher`). ## The legacy listener ```java void statusChanged(org.gradle.tooling.ProgressEvent event); ``` Here `ProgressEvent` has essentially one useful method, `getDescription()`, returning a free-form string. There is no start/finish distinction, no operation identity, no result, no category. You can drive a single status label ("Configuring projects…") but nothing structured. It predates the events package and is retained for backward compatibility. ## The structured events API ```java void addProgressListener( org.gradle.tooling.events.ProgressListener listener, OperationType... operationTypes); ``` Its `statusChanged(events.ProgressEvent)` delivers a real hierarchy: - base `ProgressEvent` → `StartEvent`, `FinishEvent` - typed pairs: `TaskStartEvent`/`TaskFinishEvent`, `TestStartEvent`/`TestFinishEvent`, `ProjectConfigurationStartEvent`/`...FinishEvent`, etc. - each event has a typed **descriptor** (operation identity, display name, parent) and the finish events carry an **`OperationResult`** (success / failure with `Failure` list / skipped / up-to-date / from-cache). This is what lets an IDE assemble a live operation tree, a test results pane, and per-task status badges. ## Choosing | Need | Use | |------|-----| | Single status string | legacy listener (or just ignore it) | | Build/task/test tree, results, timing | structured events listener | | Filter by category | structured (`OperationType`) | | Talk to a very old daemon | legacy may be all that's available | In practice every serious integration (IntelliJ, Buildship, CI tools) uses the **structured** API. Reach for the legacy one only to surface a coarse activity message or when constrained by an ancient Gradle version. ## Gotcha Because both interfaces and both `ProgressEvent` classes share names, import the right package. Mixing them up is a common compile-time confusion.
- Why can't you build a test-results tree from the legacy listener?The legacy ProgressEvent only exposes a description string — no operation identity, parent linkage, start/finish distinction, or result — so there's nothing structured to assemble a tree from.
- Both interfaces are named ProgressListener; what practical problem does that cause?It causes import ambiguity at compile time. You must explicitly import org.gradle.tooling.events.ProgressListener (and events.ProgressEvent) for the structured API, or you'll accidentally bind to the legacy one.
saying these in an interview costs you the question
- Saying the legacy listener gives start/finish events — it only gives a description string.
- Believing the events-package API is deprecated; it's the opposite — it's the modern, preferred one.