Why plugin jars explode
A Minecraft plugin runs in a shared classloader with every other plugin on the server. If two plugins ship the same library with different versions, the winner is whoever loaded first — which makes behavior depend on join order. That is a debugging nightmare.
Two solutions: relocate or don't ship
The robust fix is shading with relocation: bundle the dependency, but rewrite its package so it cannot collide with anyone else's copy.
plugins {
java
id("com.gradleup.shadow") version "8.3.0"
}
shadowJar {
// rewrite the package so no other plugin can conflict with it
relocate("com.google.gson", "dev.cardinal.libs.gson")
minimize()
}
The relocation prefix should be unique to you — dev.cardinal.libs.* is the convention here.
minimize() drops unused classes, which keeps the jar small. Be careful with reflection-heavy libraries (some databases, most serializers): minimization can strip classes that are only found by name. When in doubt, exclude them from minimization:
shadowJar {
exclude("com/google/gson/reflect/**")
}
The other option: don't bundle at all
If the platform already provides the library (Spigot ships Gson, for example), depend on it with compileOnly and never bundle it:
dependencies {
compileOnly("com.google.code.gson:gson:2.11.0") // provided by the server
}
The jar stays tiny and there is nothing to relocate. The cost: you are tied to whatever version the platform runs.
The rule of thumb
- The library is guaranteed present on the server →
compileOnly, no shading. - The library is not present → shade it, and always relocate.
- Never use
implementationwithout shading for a plugin that will share a server with other plugins.
Verify
After building, check the jar actually contains the relocated classes and no stray copies of your dependency in the original package:
jar tf build/libs/plugin.jar | grep gson