Table of Contents
Reflection
Compared to regular Java development, since Minecraft has been remapped between official (obfuscated), intermediary, and mapped (yarn or mojmap) names, finding particular fields or methods for reflection and method handles require special caution. If classes are found by literal string names, as in Class.forName, they need special attention as well. (Constant references like MinecraftClient.class are handled by remappers)
Remapping
See also: mappings
It is recommended to refer to methods and fields by their intermediary names when looking them up for reflection because intermediary names largely stay constant across Minecraft versions (thanks to matching efforts) and can allow your reflection code to potentially work in a new Minecraft version without having to release a new update.
An example:
// We will use the resolver to convert from intermediary to runtime names MappingResolver resolver = FabricLoader.getInstance().getMappingResolver(); Class<?> cls; // An example of converting intermediary class name to runtime class name cls = Class.forName(resolver.mapClassName("intermediary", "net.minecraft.class_2960")); // Alternatively, you can just use the class constant reference, which remapper will handle: cls = Identifier.class; // Now we want to create a method handle to Identifier.getNamespace: MethodHandles.Lookup lookup = MethodHandles.publicLookup(); // it is a public method MethodHandle namespaceGetter = lookup.findVirtual(cls, resolver.mapMethodName( "intermediary", // An example of unmapping, simply yields "net.minecraft.class_2960" resolver.unmapClassName("intermediary", cls.getName()), "method_12836", "()Ljava/lang/String;" ), MethodType.methodType(String.class) );
Records
Since 1.18, vanilla Minecraft starts using records in code. However, since Minecraft is processed by Proguard, which removes the record information from the Minecraft classes, these record classes will have such behaviors at runtime, despite being decompiled as records in source:
recordClass.isRecord() == false recordClass.getRecordComponents() == null
As a consequence, you cannot find the record components of vanilla classes with reflection.
See the JDK 17 API docs for isRecord() and getRecordComponents().
In addition, proguard also removes the signature (which indicates the generic information) from the record classes (but not from their methods). Yarn mappings defines signatures mappings for these classes. This affects reflection results on calls to some reflection methods, such as recordClass.getTypeParameters(), and calling getGenericReturnType() on methods that refer to the absent type parameters on records may cause MalformedParameterizedTypeException.