skip to content

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

level: middleimportance: should knowfreq 40%

answer

  1. model(type) -> ModelBuilder<T>
  2. getModel == model(type).get()
  3. withArguments / setJvmArguments / progress listener
  4. forTasks before building model
  5. get() sync vs get(handler) async

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().

solid answer

~30 s

`getModel(type)` is a convenience for the common case. When you need control you call `connection.model(type)` to get a **`ModelBuilder<T>`** — a `LongRunningOperation` you configure fluently before fetching. You can `withArguments(...)` (Gradle CLI args like `--offline`), `setJvmArguments(...)`, `setEnvironmentVariables(...)`, set `setStandardOutput/Error`, attach `addProgressListener(...)` for build events, and `forTasks(...)` to run tasks before the model is built. Fetch synchronously with `.get()` or asynchronously with `.get(ResultHandler)`. This is also where you opt into newer behavior — for example forcing the daemon to configure with specific properties. Reuse the same `ProjectConnection`; only the per-request builder carries the customization.

code

java · 6 lines
java
IdeaProject idea = connection.model(IdeaProject.class)
    .withArguments("--offline")
    .setJvmArguments("-Xmx1g")
    .forTasks("generateProto")        // run a task before reading the model
    .addProgressListener(e -> System.out.println(e.getDisplayName()))
    .get();

go deeper

for a junior

Know that getModel is the simple form and model(type) is the configurable one.

for a middle

List the key configuration knobs (arguments, JVM args, progress listener, forTasks) and sync vs async fetch.

for a senior

Explain how IDEs rely on this for offline/custom-property imports and cancellation.

for a principal

Discuss LongRunningOperation as the shared base and how operation configuration stays consistent across model/build/test launchers.

## Two ways to ask for a model `ProjectConnection` exposes: - `T getModel(Class<T>)` and `getModel(Class<T>, ResultHandler<? super T>)` — fire-and-forget convenience. - `<T> ModelBuilder<T> model(Class<T>)` — returns a configurable builder. `getModel(type)` is literally shorthand for `model(type).get()`. Reach for `model(type)` whenever you need more than the default. ## What ModelBuilder lets you configure `ModelBuilder<T>` extends `LongRunningOperation` (the same base shared by `BuildLauncher`, `TestLauncher`, and `BuildActionExecuter`), so it carries the full operation-configuration surface: - **Arguments**: `withArguments("--offline", "-Pkey=value")` — passes Gradle command-line arguments. - **JVM/daemon**: `setJvmArguments("-Xmx512m")`, `setEnvironmentVariables(map)`. - **System properties / init scripts** via arguments. - **Output**: `setStandardOutput(out)`, `setStandardError(err)`, `setStandardInput(in)`, `setColorOutput(false)`. - **Events**: `addProgressListener(listener, OperationType...)` to receive task/test/configuration progress while the model is computed. - **Run tasks first**: `forTasks("clean")` makes Gradle execute those tasks before building the model (useful when a model depends on generated sources). - **Cancellation**: `withCancellationToken(token)`. Then fetch: ```java ModelBuilder<IdeaProject> builder = connection.model(IdeaProject.class); builder.withArguments("--offline") .setJvmArguments("-Xmx1g") .addProgressListener(event -> log(event.getDisplayName())); IdeaProject idea = builder.get(); // blocking // or async: builder.get(new ResultHandler<IdeaProject>() { public void onComplete(IdeaProject result) { /* ... */ } public void onFailure(GradleConnectionException e) { /* ... */ } }); ``` ## Why it matters IDEs use this to import projects exactly the way the user configured them: offline mode, custom Gradle properties, a specific JDK, or to force a `forTasks` run so generated stubs exist before the IDEA model is read. The builder is per-request; the connection is long-lived and reused. ## Async vs sync `.get()` blocks the calling thread. `.get(resultHandler)` returns immediately and invokes the handler on a Tooling-API thread — IDEs prefer this to keep the UI responsive, often combined with a `CancellationToken` so a user can abort an import.

  • Why might you call forTasks(...) on a ModelBuilder before get()?
    So Gradle executes those tasks (e.g. code generation) before the model is built, ensuring the model reflects generated sources/classpath entries.
  • How do you fetch a model without blocking the caller thread?
    Use the asynchronous get(ResultHandler) overload; it returns immediately and invokes onComplete/onFailure on a TAPI thread, optionally with a CancellationToken.

saying these in an interview costs you the question

  • Saying getModel can take arguments — it can't; you need model(type)
  • Confusing forTasks (run tasks then build model) with newBuild (run only)
  • Reconnecting a new ProjectConnection per model instead of reusing one

context