skip to content

Tooling API

The embedding API that IDEs and CI tools use to run builds, query models, stream progress, and launch individual tests. Interviewers ask because it explains what your IDE is actually doing when it syncs.

on this pageshow

explore

questions

26

What is the Gradle Tooling API's GradleConnector, and how do you obtain a ProjectConnection from it?

level: juniorimportance: must knowfreq 45%

answer

  1. newConnector() factory
  2. forProjectDirectory required
  3. connect() -> ProjectConnection
  4. build runs in daemon, separate JVM
  5. ProjectConnection is Closeable

basics

~10 s

GradleConnector is the Tooling API entry point. Call GradleConnector.newConnector(), point it at a project with forProjectDirectory(dir), then connect() to get a ProjectConnection you use to run or query the build.

solid answer

~40 s

The Tooling API lets a non-Gradle program (an IDE, a CI tool, a custom launcher) embed Gradle and drive builds in another process. `GradleConnector` is its factory: `GradleConnector.newConnector()` returns a fresh connector you configure, most importantly `forProjectDirectory(File)` to say which project to connect to. Calling `connect()` returns a `ProjectConnection`, the handle through which you actually do work — build a model, launch tasks, run tests. The connector spins up (or reuses) a Gradle daemon and the build runs in that separate JVM, so your tool stays isolated from the build's classpath. A `ProjectConnection` holds resources (the daemon connection), so it is `Closeable` and must be closed — typically with try-with-resources.

code

java · 9 lines
java
import org.gradle.tooling.GradleConnector;
import org.gradle.tooling.ProjectConnection;
import java.io.File;

try (ProjectConnection connection = GradleConnector.newConnector()
        .forProjectDirectory(new File("/path/to/project"))
        .connect()) {
    // run tasks, build models, etc.
}

go deeper

for a junior

Name GradleConnector.newConnector(), forProjectDirectory, connect() returning a ProjectConnection.

for a middle

Add that the build runs in a daemon in a separate JVM and ProjectConnection is Closeable.

for a senior

Discuss distribution selection (useGradleVersion vs useInstallation vs useBuildDistribution) and resource lifecycle.

for a principal

Frame it as the embedding contract IDEs/CI rely on, and how isolation and daemon reuse affect tooling architecture.

## What the Tooling API is The **Tooling API** is a Java client library that lets an external program embed and control Gradle without shelling out to the CLI. IDEs (IntelliJ IDEA, Eclipse Buildship) use it to import projects, run tasks, and fetch build models; you can use it to script builds programmatically. The build does **not** run inside your process. The Tooling API connects to a **Gradle daemon** (a long-lived background JVM) and the build executes there. This keeps your application's classpath isolated from the build's, and lets the daemon cache state across invocations. ## The entry point: GradleConnector `GradleConnector` is the factory. The flow is always: 1. `GradleConnector.newConnector()` — create a connector. 2. Configure it: which project, which Gradle distribution. 3. `connect()` — returns a `ProjectConnection`. 4. Use the `ProjectConnection` to do work. 5. `close()` the connection (and optionally `disconnect()` the connector). ### Pointing at a project `forProjectDirectory(File projectDir)` is required — it names the directory containing the build you want to drive. ### Choosing the Gradle distribution By default the connector downloads/uses a Gradle version it picks. You usually override that: - `useGradleVersion("8.7")` — download and use a specific version. - `useInstallation(File gradleHome)` — use an existing local Gradle install. - `useDistribution(URI)` — use a distribution at a URL. - `useBuildDistribution()` — use the version the project's wrapper declares (honor the wrapper). ## ProjectConnection `ProjectConnection` is the working handle. From it you obtain the other Tooling API operations (model builders, build launchers, test launchers — covered by sibling topics). It implements `Closeable`; failing to close it leaks the daemon connection. ```java try (ProjectConnection connection = GradleConnector.newConnector() .forProjectDirectory(new File("/path/to/project")) .connect()) { // use connection here } ``` ## Mental model Think of `GradleConnector` as `DriverManager` and `ProjectConnection` as the JDBC `Connection`: a factory you configure once, producing a resource-holding handle you must close.

  • Does the build run inside your application's JVM?
    No. The Tooling API connects to a Gradle daemon and the build runs in that separate process, isolating your classpath from the build's.
  • What is the minimum you must configure before connect()?
    The project directory via forProjectDirectory(File). Everything else (Gradle version, args) has defaults.

GradleConnector is like JDBC's DriverManager and ProjectConnection is like the Connection it hands you — configure the factory, get a resource-holding handle, close it when done.

saying these in an interview costs you the question

  • Saying the Tooling API runs the build in your own process
  • Forgetting that ProjectConnection must be closed

context

open as a page

What are the built-in models the Gradle Tooling API exposes (GradleProject, EclipseProject, IdeaProject), and how do you query one from a ProjectConnection?

level: juniorimportance: must knowfreq 55%

basics

~10 s

The Tooling API ships read-only model interfaces like GradleProject, EclipseProject and IdeaProject. You call connection.getModel(EclipseProject.class) on a ProjectConnection to fetch a snapshot describing the build's structure.

open as a page

When driving a Gradle build through the Tooling API, how do you receive progress updates so an IDE can show build feedback?

level: juniorimportance: must knowfreq 45%

basics

~10 s

Register a ProgressListener on the build launcher via addProgressListener(...). Gradle then streams ProgressEvent objects to your callback as the build runs, which the IDE turns into progress UI.

open as a page

How do you run a Gradle build programmatically with the Tooling API using BuildLauncher, and how do you tell it which tasks to execute?

level: juniorimportance: must knowfreq 55%

basics

~10 s

Get a ProjectConnection, call newBuild() to get a BuildLauncher, then forTasks("build") to select tasks, and run() to execute it synchronously.

open as a page

What is the Tooling API's TestLauncher, and why would an IDE use it instead of running a regular Gradle test task?

level: juniorimportance: must knowfreq 45%

basics

~20 s

TestLauncher is a Tooling API entry point (connection.newTestLauncher()) that runs specific tests by class or method, instead of invoking a whole test task. IDEs use it to run or debug a single test the user selected.

open as a page

Why must a ProjectConnection be closed, and what is the idiomatic way to ensure that happens?

level: middleimportance: must knowfreq 40%

basics

~10 s

ProjectConnection implements Closeable and holds a connection to the Gradle daemon. If you do not close it you leak resources. Use try-with-resources so close() runs even on exceptions.

open as a page

Explain OperationType in the Tooling API events package and how it controls which build events you receive.

level: middleimportance: must knowfreq 35%

basics

~10 s

OperationType is an enum (TASK, TEST, PROJECT_CONFIGURATION, etc.) you pass to addProgressListener. Gradle only delivers events whose category you subscribed to, so you filter the stream at registration time.

open as a page

How do you pass command-line arguments and capture build output (stdout/stderr) when launching a Gradle build through the Tooling API?

level: middleimportance: must knowfreq 50%

basics

~10 s

Use withArguments("--info", "-PsomeProp=x") to pass CLI args, and setStandardOutput(stream)/setStandardError(stream) to redirect the build's output into your own streams.

open as a page

How do you select which tests run with TestLauncher — what's the difference between withJvmTestClasses and withJvmTestMethods?

level: middleimportance: must knowfreq 40%

basics

~10 s

withJvmTestClasses(className...) runs every test in the named class; withJvmTestMethods(className, methodName...) narrows it to specific methods. Both are accumulative — multiple calls add more selections.

open as a page

How do you control which Gradle distribution a GradleConnector uses, and when would you choose each option?

level: middleimportance: should knowfreq 35%

basics

~10 s

On the connector: useGradleVersion("8.7") downloads a version, useInstallation(dir) uses a local install, useDistribution(uri) uses a URL, and useBuildDistribution() honors the project's wrapper. Pick based on reproducibility needs.

open as a page

When would you query EclipseProject vs IdeaProject vs GradleProject, and what does each expose?

level: middleimportance: should knowfreq 32%

basics

~10 s

GradleProject is the generic, tool-agnostic view (project tree, tasks). EclipseProject and IdeaProject are IDE-shaped views adding classpath, source dirs, dependencies, and (for IDEA) JDK/language level — used by Buildship and IntelliJ respectively.

open as a page

How does ModelBuilder differ from getModel, and what can you configure on it before fetching a model?

level: middleimportance: should knowfreq 40%

basics

~10 s

connection.model(Type.class) returns a ModelBuilder you can customize — JVM args, build arguments, system properties, progress/output listeners — before calling get(). getModel(Type.class) is the no-config shortcut equivalent to model(Type).get().

open as a page

Contrast the typed events-package ProgressListener with the legacy org.gradle.tooling.ProgressListener. When would you use each?

level: middleimportance: should knowfreq 25%

basics

~10 s

The 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.

open as a page

How do you run a Gradle build asynchronously with the Tooling API, and how do you receive its success or failure?

level: middleimportance: should knowfreq 42%

basics

~10 s

Call run(ResultHandler) instead of run(). It returns immediately and invokes onComplete(Void) on success or onFailure(GradleConnectionException) on failure, on a background thread.

open as a page

How does an IDE debug a single test through the Tooling API? Explain debugTestsOn(port) and what happens under the hood.

level: middleimportance: should knowfreq 35%

basics

~10 s

TestLauncher.debugTestsOn(port) starts the test JVM with a JDWP debug agent that connects back to the IDE's debugger listening on that port. The IDE's breakpoints then hit in the test process.

open as a page

Beyond the project directory and distribution, what connection-level settings can you configure on a GradleConnector or ProjectConnection, and how?

level: seniorimportance: should knowfreq 25%

basics

~20 s

On the connector you can set the Gradle user home with useGradleUserHomeDir(File). Per operation (via the ProjectConnection) you set JVM args, build args, environment, JAVA_HOME, stdout/stderr and cancellation through the LongRunningOperation methods like setJvmArguments and withArguments.

open as a page

What problem does a BuildAction solve over repeated getModel calls, and how do you use BuildActionExecuter to aggregate models across a multi-project build?

level: seniorimportance: should knowfreq 28%

basics

~10 s

A BuildAction runs your code inside the daemon with a BuildController, so you can fetch and combine many per-project models in one round trip instead of calling getModel repeatedly. You run it with connection.action(buildAction).run().

open as a page

How do you expose a custom model to Tooling API clients using ToolingModelBuilder and the ToolingModelBuilderRegistry?

level: seniorimportance: should knowfreq 30%

basics

~10 s

Write a ToolingModelBuilder that returns your model for a given model name, register it in a plugin via the ToolingModelBuilderRegistry, and clients fetch it with connection.getModel(YourModel.class). The model interface must be on both classpaths.

open as a page

How do you extract meaningful information (task path, test identity, outcome, timing) from the events a Tooling API ProgressListener receives?

level: seniorimportance: should knowfreq 20%

basics

~10 s

Read event.getDescriptor() for identity (display name, parent, task path or test method) and, on a FinishEvent, event.getResult() for outcome plus start/end times. Cast to the typed subtype (TaskFinishEvent, TestFinishEvent) for category-specific detail.

open as a page

What threading and performance considerations apply to a Tooling API ProgressListener, and how do you handle them in an IDE?

level: seniorimportance: should knowfreq 22%

basics

~10 s

Gradle invokes statusChanged on its own internal thread, not your UI thread, and synchronously in the event path. Keep the callback fast and non-blocking, and marshal any UI updates onto the IDE's UI thread.

open as a page

How do you cancel an in-progress Gradle build started through the Tooling API, and what does an IDE need to wire up to support a Stop button?

level: seniorimportance: should knowfreq 38%

basics

~10 s

Create a CancellationTokenSource, pass its token to the launcher via withCancellationToken(token), and call source.cancel() to request cancellation. The build then fails with a BuildCancelledException.

open as a page

How would you implement 're-run failed tests' from a previous Tooling API run? Explain withTests and TestOperationDescriptor.

level: seniorimportance: should knowfreq 25%

basics

~10 s

During the first run, attach a progress listener for TEST events and collect the TestOperationDescriptors of failed tests. On the next run, pass those descriptors to TestLauncher.withTests(...) to re-execute exactly those tests.

open as a page

Contrast running tests via TestLauncher with running them via BuildLauncher.forTasks("test") plus the --tests filter. When does each make sense?

level: seniorimportance: should knowfreq 22%

basics

~20 s

TestLauncher selects tests declaratively and lets Gradle find the right task, with structured test events and per-test debug. BuildLauncher.forTasks("test") runs a named task and you narrow it with the --tests command-line filter. TestLauncher is purpose-built for IDE test execution.

open as a page

How should a long-running host (like an IDE) manage GradleConnector and ProjectConnection lifecycles across many operations?

level: seniorimportance: nice to knowfreq 18%

basics

~20 s

A single ProjectConnection can run many operations, so reuse it per project rather than opening one per task. Open lazily, keep it while the project is open, and close it when the project closes. Daemons are pooled and reused underneath.

open as a page

When building IDE integration, when would you launch work with BuildLauncher.forTasks() versus running a BuildAction, and what trade-offs guide the choice?

level: seniorimportance: nice to knowfreq 22%

basics

~10 s

Use BuildLauncher.forTasks() to execute named tasks (the build). Use connection.action(BuildAction) when you need to run logic inside the build and return a computed value, optionally also executing tasks via forTasks() on the action executer.

open as a page

What error cases and edge conditions should an IDE integration handle when using TestLauncher (no matching tests, version compatibility, failure reporting)?

level: seniorimportance: nice to knowfreq 18%

basics

~20 s

Handle 'no matching tests' (selection matched nothing fails the run), test failures surfaced as a build failure with details from TEST events, and Tooling-API-vs-Gradle version compatibility. Always capture events for accurate reporting and propagate exceptions to the UI.

open as a page