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.