skip to content

How does the 'run' task build the classpath it launches with, and how would you customise it (e.g. add a config directory, change JVM args, or set the working directory)?

level: middleimportance: must knowfreq 50%

answer

  1. run = JavaExec
  2. classpath = sourceSets.main.runtimeClasspath
  3. runtimeClasspath = classes + impl/api/runtimeOnly
  4. classpath += files(...) — append, don't replace
  5. jvmArgs / systemProperty / workingDir / args

basics

~20 s

The 'run' task is a JavaExec whose classpath is the main source set's runtimeClasspath (compiled classes + runtime dependencies). You customise it by configuring the run task: tasks.named<JavaExec>("run") { jvmArgs(...); workingDir = ...; classpath += files(...) }.

solid answer

~30 s

`run` is a `JavaExec` task. The plugin sets its `classpath` to `sourceSets.main.runtimeClasspath` — that is the compiled `main` output plus every dependency in the `runtimeClasspath` configuration (which resolves `implementation`, `runtimeOnly`, and `api`). Because the task depends on that classpath, Gradle compiles and resolves before forking the JVM. To customise, configure the task lazily: add a config dir with `classpath += files("config")`, append flags with `jvmArgs("-Dx=y")`, set `systemProperty(...)`, or change `workingDir`. Use `tasks.named<JavaExec>("run") { ... }` so you do not force eager creation. Avoid overwriting `classpath` wholesale — append to it, or you will drop the runtime dependencies the app needs.

code

kotlin · 6 lines
kotlin
tasks.named<JavaExec>("run") {
    classpath += files("config")          // append a config dir
    systemProperty("env", "dev")
    jvmArgs("-Xss2m")
    workingDir = layout.projectDirectory.dir("runtime").asFile
}

go deeper

for a junior

Know run is a JavaExec using the runtime classpath and that you configure the task to tweak it.

for a middle

Explain runtimeClasspath composition and customise jvmArgs/workingDir/classpath append correctly.

for a senior

Reason about lazy configuration (named vs getByName), Provider-wired classpath, and register sibling JavaExec tasks cleanly.

for a principal

Set conventions for launch tasks across modules and avoid eager-creation/classpath-clobbering anti-patterns in shared convention plugins.

## runtimeClasspath: where the classpath comes from The `java` plugin defines, per source set, a resolvable configuration called `runtimeClasspath`. For `main` it extends the dependency configurations and produces the full set of files needed *at runtime*: your compiled classes plus every transitive runtime dependency contributed through `implementation`, `api`, and `runtimeOnly`. The application plugin's `run` task is a `JavaExec` whose `classpath` is wired to exactly `sourceSets.main.get().runtimeClasspath`. Wiring it as a `Provider` means Gradle automatically establishes the task dependency on `compileJava`/`processResources` and resolves the configuration lazily. ## Configuring the run task `JavaExec` exposes the standard process knobs. Configure them lazily so you never trigger eager task creation: ```kotlin tasks.named<JavaExec>("run") { // append, never replace, the classpath classpath += files("config") // JVM-level jvmArgs("-Xss2m") systemProperty("spring.profiles.active", "dev") // process-level workingDir = layout.projectDirectory.dir("runtime").asFile environment("LOG_DIR", "/var/log/app") // program args args("--verbose") } ``` Key points: - **`classpath += files(...)`** appends. Writing `classpath = files(...)` *replaces* it and silently drops all runtime dependencies — a classic NoClassDefFoundError trap. - **`jvmArgs` / `systemProperty`** add to whatever `applicationDefaultJvmArgs` already contributed for the run task. - **`workingDir`** defaults to the project directory; set it when the app resolves relative paths. - **`args(...)`** appends program arguments equivalent to `--args` but committed in the build. ## Multiple run-like tasks Because `run` is just a `JavaExec`, you can register sibling tasks for alternate entry points reusing the same classpath: ```kotlin tasks.register<JavaExec>("runTool") { classpath = sourceSets.main.get().runtimeClasspath mainClass = "com.example.Tool" } ``` This keeps the application plugin for the primary entry point while giving secondary launchers without abusing it.

  • What goes wrong if you write classpath = files("config") on the run task?
    You overwrite the entire classpath with just that directory, dropping all runtime dependencies and the compiled main output. The app fails at launch with NoClassDefFoundError. Always append with += instead.
  • Why use tasks.named("run") instead of tasks.getByName("run")?
    named returns a lazy TaskProvider and configures the task only if it is in the task graph, avoiding eager creation. getByName forces the task to be realized immediately, hurting configuration time.
  • Which dependency configurations feed runtimeClasspath?
    implementation, api, and runtimeOnly all contribute; compileOnly does not (it is compile-time only). The runtimeClasspath configuration resolves them into the actual files.

saying these in an interview costs you the question

  • Replacing classpath instead of appending, dropping dependencies.
  • Saying compileOnly dependencies appear on the run classpath.
  • Forcing eager creation via getByName in performance-sensitive builds.

context