What does minimizeJar do in maven-shade-plugin, and what are its risks?
answer
- tree-shake unreachable dep classes
- static analysis only
- breaks reflection/SPI/DI loading
- ClassNotFoundException at runtime
- filters/includes to force-keep
basics
~10 sminimizeJar makes shade strip out dependency classes that aren't reachable from your code, shrinking the uber-JAR. The risk is it may remove classes only loaded via reflection or SPI, causing ClassNotFoundException at runtime.
solid answer
~30 s`<minimizeJar>true</minimizeJar>` tells shade to run static reachability analysis from your project's classes and drop any dependency classes that are never referenced, producing a much smaller uber-JAR. The danger is that the analysis only sees *static bytecode references*; classes loaded by **reflection**, **ServiceLoader/SPI**, dependency-injection frameworks, or named only in config/XML look unused and get removed, yielding `ClassNotFoundException`/`NoClassDefFoundError` at runtime. To compensate, you add `<filters>` with `<includes>` to force-keep specific artifacts or class patterns. minimizeJar is worth it for size-sensitive deliverables (CLIs, lambdas) but needs thorough runtime testing; for complex frameworks (Spring, Hibernate) it's often more trouble than the saved megabytes.
code
xml · 9 lines<configuration>
<minimizeJar>true</minimizeJar>
<filters>
<filter>
<artifact>org.postgresql:postgresql</artifact>
<includes><include>**</include></includes>
</filter>
</filters>
</configuration>go deeper
Knows minimizeJar shrinks the JAR by removing unused classes.
Understands it's static analysis and can add a filter to keep an artifact.
Weighs size savings vs reflection/SPI breakage and mandates integration testing of the minimized artifact.
Decides org policy on when fat JARs may be minimized (e.g. only simple CLIs/lambdas) and bans it for reflection-heavy services.
## What it does By default the uber-JAR contains *all* classes from included dependencies. `<minimizeJar>true</minimizeJar>` enables **tree-shaking**: shade computes which dependency classes are actually reachable from your project's own classes (following static references) and **removes everything else**. This can dramatically shrink the artifact. ```xml <configuration> <minimizeJar>true</minimizeJar> </configuration> ``` ## Why it's risky The reachability analysis is **static** — it only follows references encoded in bytecode. It cannot see classes that are resolved by *name at runtime*: - **Reflection**: `Class.forName("...")`, DI containers, ORM entity scanning. - **SPI / ServiceLoader**: provider classes listed in `META-INF/services` but never directly referenced. - **Configuration-driven loading**: class names in XML/properties/annotations processed at runtime. Such classes look dead and get deleted, so the app fails with `ClassNotFoundException` or `NoClassDefFoundError` only when that code path runs in production. ## Mitigation: filters Force-keep what the analyzer can't see: ```xml <configuration> <minimizeJar>true</minimizeJar> <filters> <filter> <artifact>org.postgresql:postgresql</artifact> <includes> <include>**</include> </includes> </filter> </filters> </configuration> ``` A `<filter>` with `<includes>**</include>` keeps an entire artifact regardless of reachability. Note: when minimizeJar is on, an *empty* filter for an artifact still keeps classes that are referenced; an explicit `**` include keeps all of them. ## Trade-offs and judgement - **Good fit**: self-contained CLIs, AWS Lambda deployments, anything where cold-start/upload size matters and the dependency graph is simple. - **Poor fit**: apps on reflection-heavy frameworks (Spring, Hibernate, Jackson polymorphic types) — the filter list becomes a maintenance burden and an outage waiting to happen. - **Always**: run the full integration/e2e suite against the minimized JAR, not just unit tests, because the failures are runtime-only. - Combine with `ServicesResourceTransformer` so SPI files survive — but you still must filter-keep the provider *classes* they name.
- How do you keep a reflectively-loaded driver when minimizeJar is on?Add a <filter> for that artifact with <includes>**</include> to force-retain all its classes.
- Why must you integration-test a minimized JAR specifically?Missing classes only surface at runtime on the affected code path; unit tests run against the full classpath and won't catch the stripping.
Like decluttering by throwing out anything you didn't touch this month — until you discover the fire extinguisher you 'never use' is now gone when you need it.
saying these in an interview costs you the question
- Claiming minimizeJar is always safe to enable
- Thinking it removes whole unused dependencies you declared (it removes unreachable classes, and SPI/reflection ones look unreachable)
- Believing static analysis can detect reflective class loading