How do you build a runnable executable fat jar with the assembly plugin, and what's needed to make `java -jar` work?
answer
- jar-with-dependencies + mainClass in <archive>
- no manifest -> 'no main manifest attribute'
- appendAssemblyId=false for clean name
- merges files: META-INF/services collide
- shade for SPI/relocation
basics
~10 sUse the jar-with-dependencies descriptorRef so all dependencies are merged into one jar, and set a Main-Class in the manifest via <archive><manifest><mainClass>. Then java -jar can find both your code and its dependencies.
solid answer
~40 sConfigure the assembly plugin with `descriptorRef` = `jar-with-dependencies` so your classes plus all dependency classes land in one jar. Crucially, set the manifest's `Main-Class` using `<archive><manifest><mainClass>com.example.Main</mainClass></manifest></archive>`, otherwise `java -jar` fails with 'no main manifest attribute'. Bind the `single` goal to the `package` phase. The result is `target/<artifactId>-<version>-jar-with-dependencies.jar`, runnable directly. Caveats: jar-with-dependencies unpacks dependency jars and merges their files, so if two dependencies ship the same `META-INF/services/...` file or signed-jar metadata, one silently overwrites the other — that breaks SPI/ServiceLoader-based libraries (JDBC, Jackson modules). For those cases the shade plugin (with `ServicesResourceTransformer` and package relocation) is the correct tool. Assembly's fat jar is fine for simple CLI tools.
code
xml · 11 lines<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>com.example.Main</mainClass>
</manifest>
</archive>
<appendAssemblyId>false</appendAssemblyId>
</configuration>go deeper
Knows jar-with-dependencies makes a fat jar.
Adds Main-Class via <archive><manifest> and binds single to package.
Anticipates file-merge/SPI collisions and knows when to switch to shade.
Defines org policy: fat-jar tooling choice, signing/SPI safety, reproducible builds.
## Goal Produce a single jar a user can run with just `java -jar app.jar`, with no external classpath. Two things must be true: 1. **All needed classes are inside the jar** — yours and your dependencies'. 2. **The jar's manifest names the entry point** via the `Main-Class` attribute. ## Step 1 — bundle dependencies The predefined `jar-with-dependencies` descriptorRef does this: it takes every dependency in scope (compile + runtime), **unpacks** their `.class` files, and writes them into one output jar alongside yours. ## Step 2 — set the entry point The `<archive>` configuration controls the generated `META-INF/MANIFEST.MF`. Set: ```xml <archive> <manifest> <mainClass>com.example.Main</mainClass> </manifest> </archive> ``` Without this you get `no main manifest attribute, in app.jar`. ## Full configuration ```xml <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-assembly-plugin</artifactId> <version>3.7.1</version> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> <archive> <manifest><mainClass>com.example.Main</mainClass></manifest> </archive> <!-- drop the -jar-with-dependencies suffix if desired --> <appendAssemblyId>false</appendAssemblyId> </configuration> <executions> <execution> <id>make-fatjar</id> <phase>package</phase> <goals><goal>single</goal></goals> </execution> </executions> </plugin> ``` ## Output naming By default the file is `target/<artifactId>-<version>-jar-with-dependencies.jar`. The `jar-with-dependencies` part is the **assemblyId** suffix; set `<appendAssemblyId>false</appendAssemblyId>` to get a clean `<artifactId>-<version>.jar` (note this then overwrites/replaces the main artifact name — be deliberate). ## The big caveat: file merging Because dependency jars are *unpacked and merged*, files that exist at the **same path** in multiple jars collide and the last one wins. The classic victims: - `META-INF/services/*` (Java `ServiceLoader`/SPI — e.g. JDBC drivers, Jackson modules) — only one provider survives. - Signed-jar signature files in `META-INF` — break signature validation. - `application.properties` / `reference.conf` (Typesafe Config) — silently clobbered. The assembly plugin does **not** merge these intelligently. The **maven-shade-plugin** does, via transformers like `ServicesResourceTransformer` and `AppendingTransformer`, and can also relocate packages. So: assembly fat jar for simple, conflict-free CLIs; shade when SPI/relocation matters; spring-boot-maven-plugin for Boot apps.
- You ship a fat jar that uses two libraries with ServiceLoader providers and only one works. Why, and how do you fix it?Both libraries have META-INF/services files at the same path; assembly overwrites one. Fix by switching to maven-shade-plugin with ServicesResourceTransformer, which concatenates the service files.
- What does appendAssemblyId=false change?It removes the assemblyId suffix (e.g. -jar-with-dependencies) from the output filename, producing artifactId-version.jar.
saying these in an interview costs you the question
- Forgetting the Main-Class manifest entry and expecting java -jar to work.
- Assuming assembly merges META-INF/services correctly — it doesn't, it overwrites.
- Believing the fat jar replaces all dependency management at runtime even for SPI-heavy stacks.