====== 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 [[https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Class.html#forName(java.lang.String)|Class.forName]], they need special attention as well. (Constant references like MinecraftClient.class are handled by remappers)
===== Remapping =====
//See also: [[tutorial: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 [[https://docs.oracle.com/en/java/javase/16/language/records.html|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 [[https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Class.html#isRecord()|isRecord()]] and [[https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Class.html#getRecordComponents()|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 [[https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/Class.html#getTypeParameters()|recordClass.getTypeParameters()]], and calling [[https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/reflect/Method.html#getGenericReturnType()|getGenericReturnType()]] on methods that refer to the absent type parameters on records may cause MalformedParameterizedTypeException.