Fast-Events makes the Forge / NeoForge event bus fast. It is a drop-in jar: no configuration, no dependencies, no Mixin, no changes to any other mod. Every event post that it takes over becomes a direct, fully inlined call chain instead of a megamorphic loop — so the cost of posting an event drops to roughly what it costs to call your listeners by hand.
It accelerates every mod's event posts, not only its own: the jar rewrites post call sites in all loaded classes, so mods that post a lot (ticking, block/entity updates, capability events, screen events) get faster without being modified.
Which file do I need?
| Minecraft | Loader | File |
|---|---|---|
| 1.20.1 | Forge 47.x | fastevents-1.20-*.jar |
| 1.21.1 | NeoForge 21.1.x | fastevents-1.21-*.jar |
One jar per game version. Nothing else is required.
The problem it solves
Both bus implementations dispatch like this:
for (EventListener listener : listeners) {
listener.invoke(event); // one call site, every listener class in the game
}
That single call site sees every listener implementation that any mod ever registered, so it is megamorphic: the JIT cannot inline any of them, and every single listener call pays a virtual dispatch plus the wrapper layers around it. On a big modpack most of the per-tick event cost is exactly this overhead, not your actual listener code.
What Fast-Events does
- Per call site, generated dispatcher. The first time a post site fires for an event class, a dispatcher is generated for that class's listener array: one field and one call instruction per listener. Each call site therefore sees exactly one receiver type and inlines all the way down into the listener body.
- No event object when nobody listens. If an event class has no listeners, the
new Event()at the call site is short-circuited and never constructed. - Same semantics. Priority order, phase markers, cancellation, generic type filters and exception handling behave exactly as in vanilla; the generated code is a straight-line expansion of what the bus would have done.
- Live cache. Dispatchers are rebuilt when a listener array changes, so listeners registered late (after a class was already dispatching) still take effect.
Numbers
Nanoseconds per post, single thread, 8 listeners on one event class, Java 21. Lower is better. Measured with the benchmark harness in the repository.
| Minecraft 1.20.1 / Forge 47.4 | vanilla | Fast-Events | |
|---|---|---|---|
@SubscribeEvent listeners |
49.6 ns | 9.3 ns | 5.3× |
addListener(this::handle), 8 different refs |
46.4 ns | 8.2 ns | 5.7× |
addListener(consumer) |
15.7 ns | 5.8 ns | 2.7× |
generic listeners (AttachCapabilitiesEvent<T> style) |
52.2 ns | 12.4 ns | 4.2× |
| 8 listeners spread over 5 priorities | — | 5.7 ns | |
| 128 listeners on one class | 201 ns | 88 ns | 2.3× |
| 512 listeners on one class | 1090 ns | 556 ns | 2× |
| Minecraft 1.21.1 / NeoForge 21.1 | vanilla | Fast-Events | |
|---|---|---|---|
@SubscribeEvent listeners |
31.2 ns | 8.8 ns | 3.5× |
addListener(consumer) |
19.0 ns | 8.8 ns | 2.2× |
new Event(), escaped (no listeners at all) |
— | ~2.4 ns |
Wider listener arrays are the common case in real packs, and they stay ahead of vanilla too. Absolute savings scale with how often a pack posts events: a listener call that used to cost 50 ns and now costs 9 ns matters when it runs thousands of times per tick across a pack.
Install
- Install Minecraft 1.20.1 with Forge 47.x, or Minecraft 1.21.1 with NeoForge 21.1.x.
- Put the matching jar into your
mods/folder. (It also works on the launcher classpath.) - That's it. No config file, no dependencies, nothing to set up.
Client and server are both supported, and both sides benefit. For a server, install it on the server; clients that join do not need it (but get a small gain if they have it too).
Optional settings
Only useful when debugging; the default configuration is what you want for playing.
| System property | Effect |
|---|---|
-Dfastevents.guard=false |
Disable the "skip new Event() when nobody listens" shortcut. |
-Dfastevents.audit=true |
Verify the cached listener array on every post and log late registrations. |
-Dfastevents.diag=true |
1.20.1 only: log listeners registered through a path that is not rewritten. |
Compatibility
- Forge 47.x (1.20.1, event bus 6.0.x / 6.2.x) and NeoForge 21.1.x (1.21.1, event bus 8.0.x). Other 1.20.x / 1.21.x builds are not supported.
- No Mixin, no access transformers, no coremod entry point that other performance mods fight over. It is a launch plugin that only replaces
post/addListenercall sites. - Does not modify Forge, NeoForge or the event bus classes themselves, and does not touch another mod's listeners or registration data.
- Safe to remove at any time — nothing is written to your world or configs.
- Not accelerated, and left on the vanilla path (so there is no behavioural difference): on 1.21,
post(EventPriority, Event); on both versions, post call sites whose bus reference cannot be resolved at load time.
FAQ
Will it change the order my listeners run in, or break @Cancelable events?
No. The generated dispatcher visits listeners in exactly the array order the bus produced (which is the priority-sorted order), and cancellation is checked at the same points as in vanilla. Mods that depend on listener ordering or on early-out after cancellation behave identically.
Does it help a pack with only a few mods?
It helps in proportion to how much event traffic you have. A single-player 1.20.1 kitchen-sink pack sees the biggest gain; a nearly vanilla instance still gains a little, because vanilla itself posts events every tick.
Can it conflict with other performance mods?
It does not patch event classes, so observability/optimizer mods that touch the bus are unaffected. If another mod also replaces post call sites, both end up wrapping the same call site; the result is still correct, just with one extra indirection.
Why is this not part of Forge / NeoForge itself?
It needs to rewrite bytecode at load time for every mod, which is not something the loader can do for its own dispatch loop without changing the API contract. As a launch plugin it can do that without touching any API.
Does it work in a modpack?
Yes. Put the correct jar for the pack's game version in mods/. Modpacks are exactly the case where the saving matters, because the number of listeners per event class grows with the mod count.
Links
- Source, issues and the benchmark harness: GitHub repository linked on this page.
- License: GNU LGPL 3.0.
Numbers above come from the repository's own benchmark harness on a quiet machine; your mileage depends on your CPU, your modpack and how much event traffic it generates.

