skip to content

Shadow Plugin (Fat/Uber Jar)

The Shadow plugin's shadowJar task: merging the runtime classpath into one jar, merging service files, relocating packages, and minimizing. Interviewers ask about relocation because it is what makes shading more than concatenation.

on this pageshow

questions

6

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%

answer

  1. single self-contained jar
  2. bundles runtimeClasspath
  3. java -jar app-all.jar
  4. com.gradleup.shadow
  5. -all classifier

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.

solid answer

~40 s

The Shadow plugin (`com.gradleup.shadow`, formerly `com.github.johnrengelman.shadow`) adds a `shadowJar` task that produces an **uber jar** (a.k.a. fat jar): it unpacks every dependency on the `runtimeClasspath` and merges those class files together with your own compiled output into one self-contained archive. The plain `jar` task only packages the module's own classes and points at dependencies via the manifest classpath, so it needs the dependency jars present at runtime. A fat jar is convenient for deployment and simple `java -jar app-all.jar` execution — no external classpath, no lib folder. By default Shadow names the artifact with an `-all` classifier and wires the main class into the manifest when the Application plugin is applied. Trade-offs: larger artifact, potential file collisions across dependencies, and licensing considerations from bundling third-party code.

code

kotlin · 8 lines
kotlin
plugins {
    application
    id("com.gradleup.shadow") version "8.3.0"
}

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

// ./gradlew shadowJar -> build/libs/app-1.0-all.jar (runnable)

go deeper

for a junior

Know that shadowJar makes one runnable jar with all dependencies inside, runnable via java -jar.

for a middle

Explain the runtimeClasspath flattening, the -all classifier, and app vs library trade-offs.

for a senior

Discuss collisions, transformers, and why fat jars hurt library publishing; mention the gradleup coordinate migration.

for a principal

Frame fat-jar usage as a packaging/distribution policy decision across services vs. libraries, with size, licensing, and reproducibility implications.

## What an uber/fat jar is A normal JAR built by Gradle's `jar` task contains only the classes and resources compiled from **your** module. At runtime the JVM still needs every third-party dependency on the classpath; you supply them via `-cp`, a `lib/` directory, or a `Class-Path` manifest entry. That's awkward for distributing a single runnable artifact. An **uber jar** (also called a **fat jar** or **shaded jar**) flattens everything into one archive: your classes **plus** the unpacked contents of every dependency JAR on `runtimeClasspath`. The result runs with nothing but `java -jar app-all.jar`. ## The Shadow plugin Shadow is the de-facto Gradle plugin for this. Historically it was `com.github.johnrengelman.shadow`; maintenance moved to the GradleUp org and the modern coordinate is **`com.gradleup.shadow`**. Applying it adds a `shadowJar` task of type `ShadowJar` (a subtype of Gradle's `Jar`). ```kotlin plugins { application id("com.gradleup.shadow") version "8.3.0" } application { mainClass = "com.example.MainKt" } ``` Running `./gradlew shadowJar` produces `build/libs/<name>-<version>-all.jar`. ## How it differs from `jar` | | `jar` | `shadowJar` | |---|---|---| | Contents | own classes only | own classes + all runtime deps | | Runnable standalone | no | yes | | Size | small | large | | Collisions | none | possible (handled by merge strategies) | ## What Shadow does under the hood 1. Resolves `configurations.runtimeClasspath`. 2. Unpacks each dependency jar and copies its entries into the output. 3. Applies **transformers** (e.g. `mergeServiceFiles()`) to combine files that would otherwise collide. 4. Optionally **relocates** packages and **minimizes** unused classes. Because `ShadowJar` extends `Jar`, you can use the normal `manifest { }`, `from(...)`, `exclude(...)` DSL on it. ## When NOT to use it Libraries published to a repository should generally publish a thin jar plus POM metadata, not a fat jar — bundling dependencies inside a library breaks downstream dependency resolution and version conflict handling. Fat jars are for **applications** and CLI tools you deploy or hand to users.

  • Why is publishing a fat jar as a library generally discouraged?
    It bundles transitive dependencies inside the artifact, so consumers lose POM-driven dependency resolution and conflict mediation, and can end up with duplicate/incompatible classes. Libraries should publish thin jars with metadata.
  • What classifier does Shadow give the output by default and where does it land?
    It uses the `all` archive classifier, producing `build/libs/<name>-<version>-all.jar` by default.

A normal jar is a recipe card that lists ingredients you must already own; an uber jar is the fully-assembled microwave meal — everything's inside, just heat and eat.

saying these in an interview costs you the question

  • Claiming the standard `jar` task already includes dependencies.
  • Saying you should always publish libraries as fat jars.

context

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

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

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