What does relocate() do in a Shadow build, and when is package relocation actually necessary?
answer
- rewrite package names via ASM
- isolate from host classpath version
- needed for libraries/plugins/agents
- not usually for standalone apps
- reflection by string breaks
basics
~10 srelocate("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.
solid answer
~50 s**Relocation** (a.k.a. shading) rewrites the bytecode and resource references of a bundled dependency from one package prefix to another — e.g. `relocate("com.google.guava", "myapp.shaded.guava")`. Shadow uses ASM to remap class names, references, and even string references in resources. The point is **conflict isolation**: if you ship a library/agent/plugin as a fat jar and the host application already has a *different* version of, say, Guava on its classpath, both copies would otherwise occupy `com.google.guava` and the JVM would load whichever wins, breaking one side. Relocating your copy to a private namespace lets the two coexist. You configure it inside `shadowJar { relocate(...) }`, optionally with `include`/`exclude` filters. Relocation is **necessary** mainly for libraries, build plugins, and agents that are loaded alongside arbitrary other code; for a standalone application fat jar that owns its whole classpath, relocation usually isn't needed. Pitfalls: reflection/`Class.forName` on string literals, service files, and resources that reference the old package name won't be remapped unless covered.
code
kotlin · 6 linesshadowJar {
relocate("com.google.common", "acme.shaded.guava")
relocate("org.objectweb.asm", "acme.shaded.asm") {
include("org.objectweb.asm.**")
}
}go deeper
Know relocate renames a dependency's packages so versions don't clash; details optional.
Explain the version-conflict scenario and that relocation rewrites references via bytecode transformation.
Discuss when it's needed (libraries/plugins/agents vs standalone apps) and reflection/service-file pitfalls.
Set org policy: standard shaded namespace, test against pinned conflicting hosts, and limit relocation to genuine collision risks.
## The problem relocation solves When you bundle dependency X version 1.0 into your fat jar and your jar is then placed on a classpath that *also* contains X version 2.0, both define classes in the same package (`com.example.x.*`). The classloader resolves each class once — you get an unpredictable mix, typically `NoSuchMethodError` or `LinkageError`. This is **classpath hell** at the package level. ## What relocate() does `relocate(pattern, destination)` instructs Shadow to **rename** a package prefix throughout the merged jar: ```kotlin shadowJar { relocate("com.google.common", "myapp.shaded.guava") relocate("org.apache.commons.lang3", "myapp.shaded.lang3") { exclude("org.apache.commons.lang3.SystemUtils") // keep one class unshaded } } ``` Under the hood Shadow walks every class with ASM and rewrites: - the class's own package, - all type references (fields, method signatures, `new`, casts), - internal name strings, - and string constants/resource paths that match (best-effort). Your own code that calls the relocated library is also rewritten, so it keeps compiling against the original names at build time but links to the shaded names at runtime. ## When you actually need it | Scenario | Relocate? | |---|---| | Standalone app fat jar (owns the whole classpath) | usually **no** | | Library published as a fat jar | **yes** for bundled transitives | | Gradle/Maven build plugin | **yes** (shares classloader with build) | | Java agent / instrumentation jar | **yes** | | IDE/host plugin | **yes** | If nothing else shares your classpath, two copies of a library can't collide, so relocation just adds complexity. ## Limits and gotchas 1. **Reflection by string**: `Class.forName("com.google.common.X")` built from a literal *may* be rewritten, but names assembled dynamically (concatenation, config) are **not** — they'll point at the now-renamed package and fail. 2. **Service files**: providers listed in `META-INF/services/...` are remapped by Shadow, but custom registry formats may not be. 3. **Native libs / resources** referencing the old path need explicit handling. 4. **Over-relocating** can break libraries that hard-code their own package in strings (e.g. some logging frameworks). ## Relationship to minimize and merge Relocation changes names; `minimize()` removes unused classes; `mergeServiceFiles()` fixes registry collisions. They're orthogonal and frequently used together in a hardened library fat jar. ## Rule of thumb Relocate only the dependencies that are likely to collide with the host environment, give them a clearly-private destination prefix (e.g. `<group>.internal.shaded.*`), and test the shaded jar against a host that pins a conflicting version.
- Why might relocation be unnecessary for a normal application fat jar?The app owns its entire runtime classpath, so there's no second copy of the same library to collide with. Relocation matters when your jar is loaded alongside arbitrary other code (libraries, plugins, agents).
- What's a failure mode relocation introduces with reflection?Dynamically-built class names — e.g. `Class.forName(prefix + name)` — aren't rewritten because Shadow only remaps recognizable references/literals, so the lookup targets the original (now-renamed) package and fails.
- How do you keep one class of a relocated package unshaded?Use the configuring overload with an `exclude(...)` filter inside the `relocate` block to skip specific classes or subpackages.
Relocation is like giving your bundled tools their own labelled toolbox so they don't get mixed up with the customer's identically-shaped tools already in the room.
saying these in an interview costs you the question
- Saying every fat jar should relocate all dependencies.
- Believing relocation rewrites every dynamically-constructed string reference.
- Confusing relocation (renaming) with minimization (removing) or merging (concatenating).