fabric-loader
fabric-loader copied to clipboard
Confusing error messages in development environment when using a class from a client-only dependency
Recently, I had the (dis)pleasure of tracking down a classloading bug during dedicated server startup that was ultimately caused by my use of a class declared in a bundled dependency that was marked as client-only. The exact error message differed from in IDE to in production, but neither message clearly identified the issue.
In development:
Caused by: java.lang.LinkageError: loader constraint violation: when resolving field "IDENTIFIER_TYPE" of type io.github.fablabsmc.fablabs.api.fiber.v1.schema.type.derived.StringConfigType, the class loader net.fabricmc.loader.impl.launch.knot.KnotClassLoader @1a72a540 of the current class, com.skaggsm.treechoppermod.FabricTreeChopper, and the class loader 'app' for the field's defining class, me.shedaniel.fiber2cloth.api.DefaultTypes, have different Class objects for type io.github.fablabsmc.fablabs.api.fiber.v1.schema.type.derived.StringConfigType (com.skaggsm.treechoppermod.FabricTreeChopper is in unnamed module of loader net.fabricmc.loader.impl.launch.knot.KnotClassLoader @1a72a540, parent loader net.fabricmc.loader.impl.launch.knot.KnotClassLoader$DynamicURLClassLoader @4e31276e; me.shedaniel.fiber2cloth.api.DefaultTypes is in unnamed module of loader 'app')
In production:
Caused by: java.lang.NoClassDefFoundError: me/shedaniel/fiber2cloth/api/DefaultTypes
at com.skaggsm.treechoppermod.FabricTreeChopper.onInitialize(FabricTreeChopper.kt:38) ~[fabric-tree-chopper-0.7.9.jar:?]
at net.fabricmc.loader.entrypoint.minecraft.hooks.EntrypointUtils.invoke0(EntrypointUtils.java:47) ~[fabric-server-launch.jar:?]
... 17 more
Caused by: java.lang.ClassNotFoundException: me.shedaniel.fiber2cloth.api.DefaultTypes
at jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:636) ~[?:?]
at jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:182) ~[?:?]
at java.lang.ClassLoader.loadClass(ClassLoader.java:519) ~[?:?]
at net.fabricmc.loader.launch.server.InjectingURLClassLoader.loadClass(InjectingURLClassLoader.java:57) ~[fabric-server-launch.jar:?]
at java.lang.ClassLoader.loadClass(ClassLoader.java:519) ~[?:?]
at net.fabricmc.loader.launch.knot.KnotClassLoader.loadClass(KnotClassLoader.java:175) ~[fabric-server-launch.jar:?]
at java.lang.ClassLoader.loadClass(ClassLoader.java:519) ~[?:?]
at com.skaggsm.treechoppermod.FabricTreeChopper.onInitialize(FabricTreeChopper.kt:38) ~[fabric-tree-chopper-0.7.9.jar:?]
at net.fabricmc.loader.entrypoint.minecraft.hooks.EntrypointUtils.invoke0(EntrypointUtils.java:47) ~[fabric-server-launch.jar:?]
... 17 more
Ideally, fabric-loader should keep a list of which classes were ignored due to environment differences and, instead of returning null
when that class is requested, throw an error highlighting that the requested class is of the wrong environment. An appropriate location for this check might be in ModResolver.findCompatibleSet
where a similar set of disabled mods is compiled as Map<String, Set<ModCandidate>> disabledMods
This may be related to https://github.com/FabricMC/fabric-loader/issues/232.
What you are primarily seeing here is incomplete class loader isolation in-dev, which is something I'm working on gradually.
The IDE/Gradle run config adds jars to the class path that don't really belong there, Loader won't put the same classes on the transforming class loader due to load constraints and thus nothing will prevent those classes from being loaded off the system/app class loader. These system/app cl loaded classes then pull more classes into the same class loader, eventually leading to the kind of error you are seeing.
The production trace is the behavior you're supposed to see once everything is nicely isolated. Going beyond that is of limited use as it is quite a niche problem based on some specific false assumptions, so not sure it's worth spending the effort. Loader doesn't really do much with disabled/inactive mods besides looking at the mod json.