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)?
answer
- run = JavaExec
- classpath = sourceSets.main.runtimeClasspath
- runtimeClasspath = classes + impl/api/runtimeOnly
- classpath += files(...) — append, don't replace
- jvmArgs / systemProperty / workingDir / args
basics
~20 sThe '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 linestasks.named<JavaExec>("run") {
classpath += files("config") // append a config dir
systemProperty("env", "dev")
jvmArgs("-Xss2m")
workingDir = layout.projectDirectory.dir("runtime").asFile
}go deeper
Know run is a JavaExec using the runtime classpath and that you configure the task to tweak it.
Explain runtimeClasspath composition and customise jvmArgs/workingDir/classpath append correctly.
Reason about lazy configuration (named vs getByName), Provider-wired classpath, and register sibling JavaExec tasks cleanly.
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.