tutorial:mixin_tips

FIXME This page is currently incomplete and still contains relatively very old edits, it may change at any time or contain incomplete or outdated advice!

Mixin Tips (WIP)

This is a collection of different tips some might find useful. It's recommended to read the intro page to better understand this page.

Extend and implement the target class's parents

When working with Mixins, you may want to use a method from the target's parent on a this instance. An easy way to make working with this relatively seamless is to directly extend the target class's parents. For instance for the target class:

public class ServerPlayerEntity extends PlayerEntity {
//...
}

Should have the following Mixin class:

public abstract class SomeMixin extends PlayerEntity {
//...
}

This page's other sections will showcase the different benefits of also making the Mixin class abstract.


Make abstract classes

It's fair to say that you should never instantiate a mixin class, as Mixin classes exist to be merged. Along with this, it may both be obnoxious and lead to errors and unintended behavior to have to implement all of the methods from your target class's parent interfaces.

Declaring a mixin class abstract doesn't affect its function, and it becomes protected against accidental instantiation and removes the burden of having to implement many of the parent's methods that you don't need:

public abstract class SomeMixin implements TargetsInterfaces {
//Does not need to implement TargetsInterfaces' methods.
}

Make abstract shadow methods

If you want to access an inaccessible method or field from your target class into your mixin class, you need to use @Shadow to make that method/field visible. References to those “Shadowed” members will be turned into their target class equivalent when merged.

You can do this as you'd expect within a standard class:

@Shadow
protected void hiddenMethod() {/*dummy body*/}

But it's arguably cleaner to use an abstract method (and hence an abstract class):

@Shadow
protected abstract void hiddenMethod(); // no dummy body necessary

:!: This doesn't work with private methods, since you can't have private abstract methods, and hence you need to implement a dummy body on those ones.


Access the ''this'' instance of your Mixin

In a Mixin class, if you want to access the this instance, you have to cast it into Object and TargetClass first:

((TargetClass)(Object)this).field/method();

But this can only work if your Mixin class extends and implements everything that the target class does. This may be an issue if one of those classes/interfaces has a method that requires implementation. However, by using an abstract Mixin class, you do not need to implement said methods.

How to mix into inner classes

Non-static inaccessible inner classes and private outer classes

Since you cannot directly access (and hence mention) these classes from outside, you need to use the “targets” field of the @Mixin annotation to specify the name. This is the same field that you would use to target a private or otherwise inaccessible outer class.

For an inner class, you do this by using the complete name of the outer class, then a $ and finally the name of the inner class, as shown below:

Class:

package some.random.package;
 
public class Outer {
     private class Inner {
         public void someRandomMethod() {}
     }
}

Mixin with injection:

@Mixin(targets = "some.random.package.Outer$Inner")
public class MyMixin {
    @Inject(method = "someRandomMethod()V", at = @At("HEAD"))
    private void injected(CallbackInfo ci) {
        // your code here
    }
}

The only caveat is that if you want to mixin into the inner class constructor, the first parameter must be of the type of the outer class (this is implicitly added by the compiler to allow the inner class to access instance methods of the outer class):

@Inject(method = "<init>(Lsome/random/package/Outer;)V", at = @At("HEAD"))
private void injected(CallbackInfo ci) {
    // your code here
}

Static inaccessible inner classes

These are the same as above, the only difference is that the constructor doesn't have the outer class first parameter (because in static inner classes only private static methods can be accessed from the inner class, and hence that parameter isn't needed).

Anonymous inner classes

These are the same as the static inaccessible inner classes, the only difference is that since they don't have a name, they are declared by appearance order, for example: the anonymous inner class if declared in our previous example class first would be named Outer$1, a second one would be named Outer$2, a third one Outer$3 and so on. It is best to check the Bytecode to be certain of the order of the Anonymous classes if any issues are encountered.


Mixing into lambdas

Sometimes you want to mix into lambdas. However, lambda do not have visible names. In this case, remember that Mixin is applied to bytecode rather than the decompiled source code. Therefore, you can view the bytecode to view the name of lambdas1).

For example, we want to inject the lambda in EntitySelectors. The target code is seen as follows:

public class EntitySelectorOptions {
  public static void register() {
    if (...) {
      putOption("name", reader -> {
        // Assume that you want to mixin to here
        int i = reader.getReader().getCursor();
        // ...
      }
      // ...
    }
  }
}

If you directly injects to register method, that will not include the lambda function. If you view the bytecode, however, you will find:

  private static synthetic method_9982(Lnet/minecraft/command/EntitySelectorReader;)V throws com/mojang/brigadier/exceptions/CommandSyntaxException 
   L0
   // ...

Therefore, the method name of the lambda you want to mixin should be method_9982. This is the common way lambdas are found in bytecode, but another format may be lambda$outerMethod$lambdaIndex. It's best to try and see which synthetic method is invoked in your target method and try to target that.

If you are using the MCDev plugin, its autocomplete for target methods will also include lambdas, and the autocomplete widget will also show parameters for each lambda, which may be leveraged to help confirm which one to target.


Mixing into those not remapped

If you want to mix into classes, methods or fields that are not remapped in yarn, you may try to add remap = false to avoid potential errors. For example:

@Mixin(value = StringReader.class, remap = false)
public abstract class StringReaderMixin { ... }

Another example is:

@Mixin(EntitySelectorReader.class)
public abstract class EntitySelectorReaderMixin {
  @Inject(method = "readTagCharacter", at = @At(value = "INVOKE", target = "Lcom/mojang/brigadier/StringReader;skipWhitespace()V", remap = false)
  // your injection method ...
}

:!: Changing the remap setting may not fix an issue that would apparently seem to be remapping related, however, so it is best to seek direct support for your specific case.

1)
See Reading the Minecraft source's relevant section on reading bytecode in your IDE
tutorial/mixin_tips.txt · Last modified: 2025/09/27 22:52 by gauntrecluse