skip to content

JVM Packaging Plugins

Turning a JVM project into something runnable and shippable: the application plugin, start scripts and installs, distribution archives, and fat jars. Interviewers ask because this is the last mile between a build and a deployable.

on this pageshow

explore

questions

page 1 of 2

How do you configure Gradle's 'application' plugin to launch an executable JVM app, and what is the minimal configuration required?

level: juniorimportance: must knowfreq 70%

answer

  1. apply 'application' → pulls in 'java'
  2. application { mainClass = ... }
  3. mainClass is a Property<String>
  4. registers the 'run' task (JavaExec)
  5. --args passes program args

basics

~10 s

Apply the 'application' plugin and set the entry point: application { mainClass = "com.example.Main" }. Then run ./gradlew run to compile and launch the app's main method.

solid answer

~30 s

Apply `application` (it pulls in the `java` plugin) and configure the `application {}` extension. The one required property is `mainClass` — the fully qualified name of the class with `public static void main`. In modern Gradle (7.5+) `mainClass` is a `Property<String>` you assign with `=`; older syntax used `mainClassName`. Once configured, the plugin adds a `run` task that compiles the code, builds the `runtimeClasspath`, and forks a JVM to execute that main class. You can pass args with `--args`, e.g. `./gradlew run --args="--port 8080"`. The plugin also wires up `installDist`/`distZip` for packaging, but the core developer loop is just `run`.

code

kotlin · 9 lines
kotlin
plugins {
    application
}

application {
    mainClass = "com.example.App"
}

// ./gradlew run --args="--port 8080"

go deeper

for a junior

Know to apply the plugin, set mainClass, and run ./gradlew run.

for a middle

Explain that 'run' is a JavaExec task, that the java plugin is applied transitively, and how --args works.

for a senior

Discuss the lazy Property<String> semantics of mainClass vs the deprecated mainClassName and why a single entry-point source of truth matters.

for a principal

Frame the plugin as the standard contract for runnable JVM services across a multi-module build and how it underpins reproducible launch configs in CI.

## What the application plugin does Gradle's built-in `application` plugin turns a project into a runnable JVM program. It is a thin layer on top of the `java` plugin: applying `application` automatically applies `java`, so you get `compileJava`, `jar`, `test`, the `main`/`test` source sets, and the standard dependency configurations. Its job is to answer one question: *which class is the entry point, and how do I launch it with the right classpath?* ## The application extension The plugin registers an extension object named `application`. Its core property: - **`mainClass`** — a `Property<String>` holding the fully-qualified name of the class containing `public static void main(String[])`. This is the only strictly required setting. Because `mainClass` is a lazy `Property`, you assign it with `=` (Kotlin DSL) and it is only resolved when actually needed. The older flat property `mainClassName` (a plain `String`) is deprecated in favour of `mainClass`. ## The run task Configuring the extension causes the plugin to register a `JavaExec` task named **`run`**. When you execute `./gradlew run`, Gradle: 1. Compiles `main` (and its dependencies via task dependencies). 2. Resolves the `runtimeClasspath` configuration into a set of files. 3. Forks a new JVM with that classpath and invokes `mainClass`'s `main` method. Pass program arguments with `--args`: ```bash ./gradlew run --args="--env prod --verbose" ``` ## Why a separate plugin instead of just JavaExec? You *could* register your own `JavaExec` task, but the application plugin standardizes the entry point, wires the classpath, and additionally provides the packaging tasks (`installDist`, `distZip`, start scripts) so the same `mainClass` drives both local runs and distributable artifacts — a single source of truth for the entry point.

  • What is the difference between mainClass and the old mainClassName?
    mainClassName was a plain String property; mainClass is a lazy Property<String> assigned with =. mainClassName is deprecated. Lazy evaluation lets the value be computed/overridden before the run task actually resolves it.
  • Does applying 'application' require also applying 'java'?
    No — the application plugin applies the java plugin for you. Adding 'java' explicitly is redundant but harmless.

saying these in an interview costs you the question

  • Saying you must register your own JavaExec task — the plugin already provides 'run'.
  • Claiming mainClass must point to a file path rather than a fully-qualified class name.
  • Insisting mainClassName is still the recommended property.

context

open as a page

What are the distZip and distTar tasks, and where do they come from in a Gradle build?

level: juniorimportance: must knowfreq 60%

basics

~10 s

distZip and distTar are tasks added by Gradle's application (and distribution) plugin. They bundle your app's jar, dependencies, and start scripts into a redistributable .zip or .tar archive in build/distributions.

open as a page

What does applying the Gradle `distribution` plugin give you, and what is the `main` distribution?

level: juniorimportance: must knowfreq 55%

basics

~10 s

Applying the distribution plugin adds a distributions {} container with one default distribution named main. It auto-creates distZip, distTar, and installDist tasks that bundle whatever you put under src/main/dist into an archive or folder.

open as a page

What does the installDist task produce, and what is the layout of its output directory?

level: juniorimportance: must knowfreq 60%

basics

~10 s

installDist assembles a runnable, unpacked installation under build/install/<name>/ with two folders: bin/ holding the generated start scripts and lib/ holding the application jar plus all runtime dependency jars.

open as a page

What does the Shadow plugin's shadowJar task produce, and why would you reach for it instead of the standard jar task?

level: juniorimportance: must knowfreq 70%

basics

~10 s

shadowJar builds a single 'fat'/'uber' jar that bundles your compiled classes plus all runtime dependencies, so the app can run with java -jar alone — the standard jar only contains your own classes.

open as a page

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%

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

open as a page

Inside a distribution's `contents { }` block, how do `from(...)` and `into(...)` work as a CopySpec to lay out arbitrary files in the archive?

level: middleimportance: must knowfreq 40%

basics

~10 s

contents is a CopySpec. from(...) declares source files/dirs; into(...) sets the destination path inside the archive. You nest them to build the archive's folder layout.

open as a page

How do you define an additional named distribution (beyond 'main') with the Distribution plugin, and how does the name affect the generated tasks and archive files?

level: middleimportance: must knowfreq 45%

basics

~10 s

Apply the distribution plugin and add an entry to the distributions { } container, e.g. create("custom"). Gradle generates customDistZip/customDistTar tasks and archives named <project>-custom.zip.

open as a page

How do you control the file name of the distZip/distTar archives, e.g. set a custom base name instead of the project name?

level: middleimportance: must knowfreq 45%

basics

~10 s

Set distributions.main.distributionBaseName (the modern API). The archive becomes <baseName>-<version>.zip/.tar. You can also override archiveFileName directly on the distZip/distTar task, but configuring the distribution is preferred.

open as a page

How does the distribution plugin name the tasks it generates per distribution, and how does that differ between `main` and a non-`main` distribution?

level: middleimportance: must knowfreq 45%

basics

~10 s

For main the tasks are distZip, distTar, installDist, assembleDist. For any other distribution named <x> Gradle capitalizes and infixes the name: <x>DistZip, <x>DistTar, install<X>Dist. The main name is elided.

open as a page

What is the CreateStartScripts task type, and what does the generated launcher script actually do at runtime?

level: middleimportance: must knowfreq 50%

basics

~10 s

CreateStartScripts is the task type behind the startScripts task. It generates the bin/<name> (Unix) and bin/<name>.bat (Windows) launchers that build the classpath from lib/, locate the JVM, apply JVM options, and run your mainClass.

open as a page

Why is mergeServiceFiles() needed in a Shadow build, and what breaks if you omit it?

level: middleimportance: must knowfreq 60%

basics

~10 s

Multiple dependencies can each ship a file at the same path under META-INF/services/. Without mergeServiceFiles() one overwrites the others, so some ServiceLoader providers vanish and features silently break.

open as a page

What is applicationDefaultJvmArgs, how does it differ from program arguments and ad-hoc JVM flags, and when is it actually applied?

level: middleimportance: should knowfreq 45%

basics

~10 s

applicationDefaultJvmArgs is a list of JVM options (like -Xmx512m) baked into the application. They apply both to the 'run' task and to the launched app, unlike --args which sets program arguments.

open as a page

When would you reach for the `distribution` plugin to package arbitrary content instead of the `application` plugin, and how do they relate?

level: middleimportance: should knowfreq 35%

basics

~10 s

Use distribution when you just need to ship arbitrary files (docs, configs) as an archive with no runtime/launcher. The application plugin is for runnable JVM apps and is built on top of distribution.

open as a page

How are distZip and distTar wired into the build lifecycle, and how does assembleDist fit in?

level: middleimportance: should knowfreq 35%

basics

~10 s

The distribution plugin adds assembleDist, which depends on distZip and distTar. It wires assembleDist into assemble/build, so ./gradlew build produces both archives. Run assembleDist to build only the distribution archives.

open as a page

How does `distributionBaseName` interact with the project version to determine archive file names, and how do you control versionless archive names?

level: middleimportance: should knowfreq 35%

basics

~10 s

The archive name is <distributionBaseName>-<version>.zip. distributionBaseName defaults to the project name and version comes from project.version. If the version is unset (unspecified), the archive is just <baseName>.zip.

open as a page

How does applicationName affect installDist, and how do you change the name of the install directory and the launcher executables?

level: middleimportance: should knowfreq 35%

basics

~10 s

applicationName (set in the application { } block) drives the install folder name (build/install/<applicationName>/) and the base names of the generated scripts (bin/<applicationName> and bin/<applicationName>.bat). It defaults to the project name.

open as a page

When you apply both the Application plugin and Shadow, how does shadowJar become runnable, and how do you make the build produce a runnable artifact by default?

level: middleimportance: should knowfreq 40%

basics

~10 s

With the application plugin applied, Shadow reads application.mainClass and writes a Main-Class manifest entry into the shadowJar, so java -jar app-all.jar works. You can wire shadowJar into the build/assembly graph so it's produced automatically.

open as a page

What does minimize() do in a Shadow build, and what risk does it introduce?

level: middleimportance: should knowfreq 45%

basics

~10 s

minimize() strips classes from bundled dependencies that aren't reachable from your code, shrinking the fat jar. The risk: it can remove classes only used reflectively or via service loading, breaking the app at runtime.

open as a page

When is the built-in application plugin the right choice versus a framework launcher like Spring Boot's bootRun, and what are common ways teams misuse it?

level: seniorimportance: should knowfreq 30%

basics

~20 s

Use the application plugin for plain executable JVM apps where you control main(). Frameworks like Spring Boot provide their own bootRun task and packaging, so you usually rely on those instead. Misuse: forcing both, or expecting application to build an executable fat jar.

open as a page

A distribution archive must be reproducible bit-for-bit and preserve correct file permissions (e.g. executable scripts) across builds. How do you configure that on the distribution/archive tasks?

level: seniorimportance: should knowfreq 22%

basics

~10 s

Set isReproducibleFileOrder = true and isPreserveFileTimestamps = false on the *DistZip/*DistTar tasks, and declare permissions with filePermissions { unix("0755") } in the CopySpec for executables.

open as a page

How would you build a distribution's contents lazily from task outputs and reuse a shared layout across several distributions, using Providers and reusable CopySpecs?

level: seniorimportance: should knowfreq 25%

basics

~10 s

Feed from(...) with task Providers (e.g. tasks.named("genDocs")) so packaging is lazy and dependency-wired. Factor common layout into a copySpec { } and reuse it via with(spec) across multiple distributions.

open as a page

How can you add extra files (README, license, config) to the distZip/distTar archive without touching the application plugin's defaults?

level: seniorimportance: should knowfreq 30%

basics

~10 s

Use distributions.main.contents { ... }, which is a CopySpec. from("...") adds files (e.g. into a etc/ or root folder) on top of the default bin/lib that the application plugin already supplies.

open as a page

When would you declare multiple entries in the `distributions {}` container, and what does each extra distribution produce?

level: seniorimportance: should knowfreq 28%

basics

~10 s

Add more entries when one project needs several differently-packaged bundles (e.g. a server and a docs bundle). Each entry gets its own distributionBaseName, its own contents CopySpec, and its own <name>DistZip/<name>DistTar/install<Name>Dist tasks.

open as a page

How does the standalone `distribution` plugin relate to the `application` plugin, and when would you apply `distribution` on its own?

level: seniorimportance: should knowfreq 30%

basics

~20 s

The application plugin applies distribution under the hood and configures the main distribution to add launcher scripts and the runtime classpath. You apply distribution alone when you need to package arbitrary files into ZIP/TAR/install bundles without a runnable Java app.

open as a page

What is executableDir in the Application plugin, and how does it change the location of the start scripts within an install?

level: seniorimportance: should knowfreq 25%

basics

~20 s

executableDir is the relative path inside the distribution where the launcher scripts are placed; it defaults to "bin". Setting it (e.g. "runtime/bin") moves the scripts there, while the scripts still resolve the app home relative to their own location.

open as a page

When would you use installDist for local verification, and how does it relate to the run task and the archive tasks?

level: seniorimportance: should knowfreq 30%

basics

~20 s

installDist materializes the exact unpacked distribution (bin/ + lib/) on disk so you can launch it via its real start script — closer to production than the run task, and faster to iterate than building and extracting distZip/distTar.

open as a page

What does relocate() do in a Shadow build, and when is package relocation actually necessary?

level: seniorimportance: should knowfreq 50%

basics

~10 s

relocate("old.pkg", "new.pkg") rewrites a dependency's package names (and all references to them) inside the fat jar, so your bundled copy can't clash with a different version of the same library on the consumer's classpath.

open as a page

How would you decide between a Shadow fat jar and an alternative packaging like Spring Boot's bootJar or a nested/exploded distribution, when standardizing build output across services?

level: seniorimportance: should knowfreq 35%

basics

~20 s

Shadow flattens dependencies into one classpath, which is great for plain JVM apps and CLIs but breaks libraries that depend on jar boundaries (signed jars, duplicate resources). For Spring apps prefer bootJar's nested layout. Choose per app type and resource-collision risk.

open as a page

How do you configure the application plugin for a JPMS (Java module) application, and what changes about mainClass and the launch?

level: seniorimportance: nice to knowfreq 22%

basics

~10 s

For a modular app you also set application { mainModule = "com.example.app" } alongside mainClass. Gradle then launches with --module-path and -m instead of the classpath, running the named module's main class.

open as a page

showing 1–30 of 31