User Tools

Site Tools


tutorial:mixin_your_first_mixin

This is an old revision of the document!


:!: This page is currently a draft! It is not intended to be read by users of the wiki yet. Feedback will be appreciated but trust this page's information at your own risk!
Contact GauntRecluse (paleintrovert) on Discord to give feedback and suggestions on this draft

Tutorial: Making your first Mixin (DRAFT)

Preamble

This is meant to be a complementary page to the Introduction to Mixins page, which you should read first to get an understanding of what Mixin is. It intends to hold a newcomer's hand through each step of creating an elementary Mixin, teaching what each element does and what it is for along the way. See Registering Mixins for setting up Mixins on a Fabric project if you intend to follow along with this.

This tutorial will be made using a 1.21.1 Fabric Project as reference. It is recommended to develop using IntelliJ IDEa Community Edition to be able to leverage the Minecraft Development plugin by demonwav AKA MCDev. Yarn mappings will be used. If you wish to follow along with Mojang mappings, you may use a tool such as mappings.dev or Shedaniel.dev's mappings tool to translate between the Yarn mappings showcased and the ones in your development environment as needed.

The repository containing this tutorial's Mixin can be found at: here, the specific Mixin class being TutorialFirstMixin

Creating the Mixin Class

For this tutorial, we'll add a Logger call at the head of the loadWorld method in the Vanilla MinecraftServer class. This will be based on the ExampleMixin Mixin class present in the Fabric mod template at the time of writing, with some tweaks.

For this task, we'll use the Mixin injector annotation @Inject, and explain the different elements as we build it. Mixin uses annotations to parse metadata that will be used to find where and how to specifically try and apply our changes.

It is important to note that we use @Inject specifically because we only need to insert a single method call at a given point. One must always consider which tool to use for the job, and many times it is not @Inject, and it may not even be right to use a Mixin in the first place.
The reason @Inject is the right tool here is because it injects a method call where specified, and that's all we need to do. It is as such the smallest, least intrusive change that can be made. This makes it the right injector for the job.

After following the necessary steps described in Registering Mixins to set up our mixins.json config, we'll create our class in our dedicated Mixin package. We'll call this class TutorialFirstMixin:

@Mixin(MinecraftServer.class)
public abstract class TutorialFirstMixin {
 
}

It is conventional practice to name your Mixin classes the target class with Mixin appended as a suffix. Here, in a normal mod, it would typically be named MinecraftServerMixin instead. This makes is very easy for someone who is looking through your project to know which Mixin classes have a given target. If you, for whatever reason, opt to not follow this convention, it is recommended to still name your Mixins after what they target in some way.

You'll notice we made the Mixin class abstract. This is a best practice when mixing into a class, as it avoids an accidental instantiation of the class, and removes the burden of needing to implement methods that may break the Mixin when implementing and extending the target's superclasses and interfaces.

Accordingly, we will also add this to our mixins.json config file. The reason Mixin uses a config rather than just finding the Mixin classes by searching your mod's jar at runtime is mainly for performance purposes. It is faster and less prone to error if the files are manually defined, compared to the very expensive operation of parsing a jar. It also has the non-negligeable benefit of giving the developer a lot more control on how they want their Mixins to be used.
Adding to our config file:

{
...
    "mixins": [
        "TutorialFirstMixin"
    ],
...
}

And that's all there is to actually creating the Mixin Class, but it won't do anything on its own.


Creating the injector

We have a Mixin class, now let's use it to do something.

The first step, assuming nothing has caused a crash or gone to hell yet, is to create a “stub” method, AKA a method that's just a placeholder for our actual method. This will be the method we annotate with @Inject, and putting it there before the annotation allows us to not be bothered by the Annotations are not allowed here IDE error from having a lone annotation.

@Mixin(MinecraftServer.class)
public abstract class TutorialFirstMixin {
 
    private void addLoggerAtHead() {
    }
}

We've named it addLoggerAtHead because of what it'll be used for, your Mixin injectors' methods should always try to best describe their intended effects on the target. If you wish to be safe with compatibility, you may also name all members of your Mixin classes by a modid$name convention to strictly distinguish them from other mods' added members. This is however not required for handler methods such as the ones annotated with @Inject.

We now add our @Inject annotation:

public abstract class TutorialFirstMixin {
 
    @Inject(method = "loadWorld")
    private void addLoggerAtHead() {
 
    }
}

This now specifies to Mixin that, when that method gets merged into MinecraftServer, we want to inject a call to it in the method loadWorld. Since there is only one method of that name in MinecraftServer, the name is enough, this would not be the case if the method had overloads. If you're using the Minecraft Development plugin, you should almost always leverage the plugin's autocomplete for a Mixin annotation's fields to ensure accuracy.

Now, to specify where in the targeted method we want our injection to happen, we add something that may be a bit counterintuitive to some, an @At annotation as one of the arguments in the @Inject annotation:

public abstract class TutorialFirstMixin {
 
    @Inject(method = "loadWorld",
            at = @At(value = "HEAD")
    )
    private void addLoggerAtHead() {
    }
}

It is not necessary to put the different fields on different lines in your code, but it may become useful when the individual arguments are very long or if you prefer it for organization purposes.

Now, let's look closer at the annotations we have thus far. Think of them as holding a series of hierarchized instructions. A very simplified explanation would be:

  • @Inject says that tells Mixin that it should inject a call to this method at specified point(s)
    • method = “loadWorld” narrows down the point of injection to within that target method
    • at = @At is used to specify what to look for in the targeted method(s)
      • value = “HEAD” specifies that Mixin should try to inject at the very start of the method at the point of injection.

Now, if you're following along with MCDev you'll likely notice that our handler method, addLoggerAtHead, does not have the correct signature for the target. In most cases, using MCDev's context action that quickly fixes it should work. For the sake of this tutorial, an @Inject injector's handler method must contain the target method's parameters – here there are none – and a CallbackInfo parameter, often called ci if injecting into a method of void return type. If we were injecting into a method that returns a value, it would instead be CallbackInfoReturnable<T> cir where T is the return type of the target method, such as a Boolean.

If we fix the signature and add a logger call, the full class becomes:

@Mixin(MinecraftServer.class)
public abstract class TutorialFirstMixin {
 
    @Inject(method = "loadWorld",
            at = @At(value = "HEAD")
    )
    private void addLoggerAtHead(CallbackInfo ci) {
        GauntTutorialMod.LOGGER.info("MinecraftServer$loadWorld has started!")
    }
}

And that's it for making this Mixin work! But let's go over some general advice that will come in handy as you learn Mixins:


General Advice

Extending the target class's parents

When the class you're targeting extends and/or implements parents, mimicking that on your Mixin class is an overall benefit, as it allows you to get all of the parent methods directly. For our example Mixin it'll look like:

public abstract class TutorialFirstMixin extends ReentrantThreadExecutor<ServerTask> implements QueryableServer, ChunkErrorHandler, CommandOutput, AutoCloseable {
    public TutorialFirstMixin(String string) {
        super(string)
    }
 
    @Inject(method = "loadWorld",
            at = @At(value = "HEAD")
    )
    private void addLoggerAtHead(CallbackInfo ci) {
        GauntTutorialMod.LOGGER.info("MinecraftServer$loadWorld has started!");
    }
}

The constructor is irrelevant to actual Mixin work, it's only there for the sake of extension. A perceptive reader might notice that we didn't implement any methods. That is another benefit of making our class abstract. Abstract classes, because they are never instantiated, do not need to implement all methods from their parents. Do note if you implement an interface not implemented by the target class, that will be merged into the target class at runtime aswell.


Debugging Mixins

The export feature

Mixin provides debugging utilities, one of them is the ability to export the transformed classes once they're loaded. This allows to be able to specifically check what our Mixin has targeted and if it has applied where we wanted it to.
This section's information is detailed upon more specifically and quicker at the Exporting Mixin Classes page.

Exporting our Mixin would look like:

@Debug(export = true)
@Mixin(MinecraftServer.class)
public abstract class TutorialFirstMixin extends ReentrantThreadExecutor<ServerTask> implements QueryableServer, ChunkErrorHandler, CommandOutput, AutoCloseable {
 
    @Inject(method = "loadWorld",
            at = @At(value = "HEAD")
    )
    private void addLoggerAtHead(CallbackInfo ci) {
        GauntTutorialMod.LOGGER.info("MinecraftServer$loadWorld has started!")
    }
}

Setting a Mixin to be exported makes it so that once the targeted class has been transformed and has loaded (which could be anytime between the client starting and a world loading) Mixin will export a decompiled version of that class, which will now contain the different transformations applied by Mixin to the class. It should be exported to the directory run/mixin.out. You can also configure your development runs to export all Mixins by adding the following argument to your VM options: -Dmixin.debug.export=true

Why do my merged Mixins' names look so weird?

One thing you will notice in an exported class is that your Mixin handler methods' names look very odd when merged into the target class. This is the result of a process known as “name mangling” or just “mangling”. This process consists of procedurally modifying the merged member names such that they are unique. This is first and foremost for compatibility reasons, as two Mixins adding methods of the same name may cause a conflict even if the injectors target entirely different things. Injector methods are also given an identifier corresponding to the type of injector as part of mangling. For our case, @Inject has the prefix handler.

:!: The @Debug annotation settings stay as they are in dev when the mod is built into a jar, make sure to not accidentally export Mixin classes in the jar you publish!

Breakpoints and MCDev

Breakpoints don't natively work with Mixin classes as they get merged at runtime. However, MCDev adds breakpoint support in Mixin Classes directly, allowing you to debug with them by directly putting them in your Mixins rather than having to try and anticipate your injector.

Loggers

Logger calls can be very handy when trying to debug complex logical flows or just for general warning and error notifications. And whilst that may be useful for debugging, one should be particularly careful when using them anywhere that'll be called a lot, for performance reasons. This applies just the same with Mixins, and one should in general not use loggers as their primary form of debugging.


Closing Thoughts and where to go next

Assuming nothing blew up, you've completed your first functioning Mixin, and hopefully you learned a bit about how Mixin is used and broadly works along the way. What now?

Further Reading

For more thorough and formal intros to the topic, you should read through the Mixin Introduction page and its linked Wikis. Prioritize the latter if possible at all times as the Fabric Wiki will inherently be less reliable than the official Wikis about Mixin tools.
Namely, those are the Mixin Wiki and the MixinExtras Wiki. The latter should always be one's first resort for anything relating to MixinExtras. You may also find further information about specific tools in the JavaDocs in the tools' source code.

Other than that, the most important step from here is trial, error, and asking for help/information when stuck. This is an entirely expected part of the learning process, and even more experienced devs frequently ask questions about how to best approach an issue with Mixins.

tutorial/mixin_your_first_mixin.1760109019.txt.gz · Last modified: 2025/10/10 15:10 by gauntrecluse