Only shade dependencies as a last resort
Shading is a process where a dependency is relocated to a different Java package
and copied into the same jar as the code that relies on that dependency.
The main purpose of shading is to avoid conflicts
between the versions of dependencies used by a library and the versions used
by the consumers of that library. Most implementations of shading do a simple
search and replace of Java package strings in the byte code of the dependency
classes and the code using the dependency classes.
There are a number of problems with shading:
- It can cause considerable size bloat. If it is applied at multiple layers,
it can have a massive accumulating effect.
- Users cannot upgrade dependencies shaded by other libraries (which means
security fixes to a transitive dependency need to wait for the shading library
to also roll in the security fix).
- Shading cannot be performed on types used in a library’s own public API as return
types or method arguments.
- Service entries under
META-INF/services can easily be messed up. They need
special effort to be merged because the entries are located in the same place
for every jar.
- Shading doesn’t relocate JNI code.
- Shaders either don’t support classes loaded by reflection or they replace all
strings matching selected packages in the bytecode, leading to the corruption
of some constants.
- It is easy to accidentally fail to relocate classes or other files, resulting in
an artifact that overlaps classes and files with the original dependency
(creating the situation described in JLBP-5).
- Shading violates many closed source licenses. If the license contains
language such as “You must not reverse engineer, decompile, disassemble,
modify, or translate the Software”, you should consult an attorney before
shading. Shading is compatible with open source licenses.
- Shading breaks code browsing and debugging in many IDEs. Eclipse, for example,
cannot locate the original source code for a shaded class.
For these reasons, shading should be used sparingly, especially for libraries
consumed by other libraries (because of the snowball effect).
One area where shading has been put to good use is in frameworks, which need to
allow user-space code to choose its own dependencies that might not be
compatible with the framework’s dependencies.
If you shade, you need intimate knowledge of how your dependencies work, and
you need to read the source code. Make sure you do all of the following:
- Add a test to make sure that all classes and other files copied into your jar
from your dependencies are relocated.
- Other files include configuration files (for example
- Promote transitive dependencies that are not relocated to direct
- Make sure no dependencies that appear on your own library surface are
- Make sure that all service files are merged correctly.
- Make sure to relocate JNI library names.
- Confirm that the licenses of the dependencies that you are shading are
compatible with being re-released within your artifact.