Class ShimLoader
internal-libs/adventure.jar inside this jar, in a
CHILD classloader.
Why a nested jar rather than shading it flat. The relocated Adventure exists only for
servers with no native Adventure, meaning everything below 1.18.2. Shading it as ordinary class
entries made it importable by every consumer, and dependency scoping cannot fix that: measured
2026-08-30, a spigot-jar consumer compiled against the shaded Adventure successfully even
with the dependency declared runtime-only, because scope metadata cannot hide bytes that are
physically present. Java's classpath has no nested-jar support, so entries inside this jar are not
classpath entries and javac cannot reach them. That survives a consumer shading this
library, which is the property scoping could not provide.
Why a child loader rather than adding it to the plugin's own. Injecting into the plugin
classloader needs URLClassLoader.addURL, and setAccessible on it fails on Java 16
and up. The only route that still works reaches MethodHandles.Lookup.IMPL_LOOKUP through
sun.misc.Unsafe, which Java 25 already warns is scheduled for removal. A child loader uses
only URLClassLoader and parent delegation, neither deprecated.
How the boundary works. Delegation is parent-first, so the child sees everything the
plugin classloader has and can implement VersionedComponent, whose Class object it
resolves by delegating upward. The parent cannot see into the child, so no plugin, no consumer and
no downstream plugin can reach the relocated Adventure. Instances cross the boundary as
VersionedComponent, which names nothing relocated.
Verified on 1.8.8, 1.16.5, 1.20.4, 1.21.11 and 26.2, across Java 8, 17, 21 and 25, including a three-plugin topology where one plugin shades this library, a second depends on that plugin, and a third shades its own copy.
-
Method Summary
Modifier and TypeMethodDescriptionstatic voidWhere to extract the nested jar.static @NotNull ClassLoaderget()The loader, extracting into whateverconfigure(File)was given, or temp.static @NotNull ClassLoaderThe classloader holding the relocated Adventure and the implementations that use it.static booleanWhether the relocated Adventure is reachable from the classloader that loaded this class.
-
Method Details
-
configure
Where to extract the nested jar. Call this before anything touches text, from the plugin'sonEnable, passing the plugin's data folder.The default is the system temp directory, which works on a developer machine and is the wrong answer on hosted servers, where temp is routinely mounted noexec or read-only. The failure that produces does not look like it has anything to do with Adventure.
-
get
The loader, extracting into whateverconfigure(File)was given, or temp. -
get
The classloader holding the relocated Adventure and the implementations that use it.- Parameters:
cacheDir- where to place the extracted jar. Use the plugin's data folder rather than the system temp directory: hosted servers routinely mount temp noexec or read-only, and the failure that produces is not obviously about Adventure at all.
-
leakedToParent
public static boolean leakedToParent()Whether the relocated Adventure is reachable from the classloader that loaded this class.It must be
false. If it is true the nested jar has been unpacked into loose classes somewhere in the chain and every consumer can import Adventure again.
-