A server-side Fabric mod that makes chat processing asynchronous and keeps chat working even when the Minecraft main thread is completely stuck.
- Async chat pipeline: chat messages are processed off the main thread (filtering, cooldown, formatting).
- Emergency fallback channel: when the main thread stops ticking, chat is intercepted directly in the Netty pipeline and sent as simplified system chat to all connected players without waiting for the server thread.
- Thread-safe player/connection registry: normal join/leave events plus Netty channel cleanup keep emergency broadcasts accurate.
- Config file:
config/asyncchat.json, hot-reloadable via/asyncchat reload. - Commands:
/asyncchat status,/asyncchat reload.
- Minecraft
26.1.2 - Fabric Loader
0.19.3 - Fabric API
0.155.2+26.1.2 - Java 25
./gradlew buildThe built jar is in build/libs/asyncchat-<version>.jar.
Put the jar in your server's mods folder and start the server. A default config is generated at config/asyncchat.json.
{
"enabled": true,
"normalThreads": 2,
"normalQueueSize": 1024,
"emergencyThreads": 1,
"emergencyQueueSize": 128,
"stuckThresholdMs": 1000,
"watchdogIntervalMs": 200,
"format": "<%player%> %message%",
"filters": [],
"cooldownSeconds": 1.0,
"logChat": false
}| Key | Description |
|---|---|
enabled |
Master switch. |
normalThreads |
Threads for normal async chat processing. |
normalQueueSize |
Max queued normal messages; excess is dropped. |
emergencyThreads |
Threads for emergency chat processing. |
emergencyQueueSize |
Max queued emergency messages; excess is dropped. |
stuckThresholdMs |
Main-thread idle time before emergency chat mode activates. |
watchdogIntervalMs |
Watchdog polling interval. |
format |
Chat format. %player% and %message% are replaced; & color codes are supported. |
filters |
List of blocked words/phrases (case-insensitive substring). |
cooldownSeconds |
Per-player chat cooldown. |
logChat |
Logs chat to the server console. |
/asyncchat status— shows current mode and tracked connections./asyncchat reload— reloadsconfig/asyncchat.json(requires admin permission).
ServerMessageEvents.ALLOW_CHAT_MESSAGEcaptures player chat on the main thread and returnsfalseso vanilla broadcasting is deferred.- A
ChatContextis submitted to a bounded worker pool. - Cooldown, filter and format processors run off-thread.
- The result is scheduled back to the server thread and broadcast through
PlayerList.broadcastChatMessage.
- A watchdog records the last main-thread
ServerTickEvents.END_SERVER_TICK. - If the main thread is idle longer than
stuckThresholdMs, the mod entersDEGRADEDmode. - A Mixin adds
ChatPacketInterceptorbefore the vanillapacket_handlerin every Netty pipeline. - In degraded mode,
ServerboundChatPacketis consumed on the Netty thread and submitted to the emergency pool. - The emergency pipeline runs and sends
ClientboundSystemChatPacketdirectly through each player'sConnection.send(...), which queues the packet on the Netty event loop — no main thread required.
When the main thread ticks again, the mod switches back to normal mode.
- Emergency chat is deliberately simplified: it is not signed chat and does not run through Fabric message events.
- Players who are not fully connected before a main-thread hang cannot join during it.
- If the native server watchdog kills a truly hung main thread, no mod can continue running; emergency chat works while the process is alive and Netty threads are still scheduled.
- The server may still appear frozen for non-chat operations while the main thread is stuck.
src/main/java/org/coffeepop/asyncchat/
├── Asyncchat.java # Mod initializer / wiring
├── chat/ # ChatContext, pipeline, processor interface
├── command/ # /asyncchat commands
├── config/ # AsyncChatConfig + ConfigManager
├── core/ # Executors + main-thread monitor
├── dispatcher/ # Normal/emergency broadcasters
├── event/ # Fabric event listeners
├── mixin/ # Connection/PlayerRegistry accessors
├── network/ # Player registry + Netty interceptor
└── processor/ # Filter, cooldown, format