Forge/Neoforge mod load order
Parallel modloading means the required="BEFORE" and required="AFTER" statements in your mod metadata toml don’t work. Forge/neoforge documentation says it’ll make the dependency load before / after your mod, but this is wrong.
Forge 1.20.1
- All mods are constructed in parallel.
- All parallel-dispatch events, including lifecycle events (
FMLxxxxxEvents), are blasted out to every mod in parallel with no respect to mod load order. - Parallel events have an
enqueueWorkfunction, but the order of execution betweenenqueueWorkblocks is unspecified, and also has nothing to do with mod load order.
Also, this confused me at first but parallel-dispatch happens when the dispatcher calls a function which dispatches the event in parallel; the ParallelDispatchEvent interface itself has nothing to do with it.
Neoforge
Parallel dispatch events now respect mod load order due to some CompletableFuture weaving. (Additionally, neoforge skips your event handler if one of your declared-dependencies threw an exception from its.)
The order of enqueueWork blocks is still unspecified(?)
In practice
Mod load ordering mainly affects these things:
- It’s a tiebreaker for sequential dispatch events when two event listeners have the same priority(?).
- Anything that calls the function
ModList#forEachModInOrder. In particular,ModLoader#postEvent, a function for sending events to every mod’s “mod event bus”
You cannot rely on the following orders between you and your dependencies, even if you declare a preferred load order:
- Mod construction order.
enqueueWorkorder.- Only on Forge, not post-1.20.1 neoforge: Lifecycle event dispatch order.
In my mods I was previously relying on mod construction order to load my content mods after my library mod, which somehow worked flawlessly in-dev without losing the race once. But end-users started to lose the race..! Since v2025.10.21, to load child mods after ModderNameLib, I do the following:
- ModderNameLib defines an
AfterQuatlibEventwhich extendsEventand implementsIModBusEvent. - ModderNameLib listens for its
FMLConstructModEventand attaches anenqueueWorkblock to it. - In the
enqueueWorkblock, it dispatchesAfterQuatlibEventusingModLoader#postEventWrapContainerInModOrder. - Mods that depend on ModderNameLib grab the “mod event bus” from their constructor and subscribe to
AfterQuatlibEvent.
This works because enqueueWork blocks always run after the parallel stage has finished; in particular FMLConstructModEvent enqueueWork blocks run immediately after all of the mods have ran their constructors.