read this first
This is the #1 cause of plugin crashes. Any code that modifies entities, components, or world state MUST execute on the World thread. Violating this will cause silent failures or hard crashes.
What We Know
Entity modifications require world.execute() to be thread-safe. Events fire on various threads (network, etc.), not necessarily the thread that owns entity data.
The exact threading architecture isn't officially documented. What we know from observation:
world.execute()queues code to run on the world threadworld.isInThread()checks if you're already on the world thread- Direct entity modification from event handlers can crash
- Packet sending is thread-safe (Netty handles it)
- Read operations are generally safe
- Commands run on
ForkJoinPool.commonPool(), not the world thread
The Problem
Events fire on different threads. If you modify entity data directly from an event handler, you risk race conditions and crashes.
This Will Crash
1// WRONG - Direct modification from event thread2getEventRegistry().register(PlayerConnectEvent.class, event -> {3 Player player = event.getPlayer();4 5 // CRASH! Event runs on network thread, not World thread6 player.setModel("Mouse");7});The Solution: world.execute()
Always wrap entity modifications in world.execute():
1// CORRECT - Execute on World thread2getEventRegistry().register(PlayerConnectEvent.class, event -> {3 Player player = event.getPlayer();4 World world = player.getWorld();5 6 // Safe! Runs on the World thread7 world.execute(() -> {8 player.setModel("Mouse");9 player.setSpeed(1.5f);10 });11});how it works
world.execute() queues your code to run on the next World tick. This guarantees safe, synchronized access to entity data.When To Use world.execute()
| operation | needs world.execute()? | reason |
|---|---|---|
| Reading player position | No | Read-only is generally safe |
| Changing player model | YES | Modifies component data |
| Sending packets | No | Connection is thread-safe |
| Modifying components | YES | All component writes |
| Spawning entities | YES | World state modification |
| Teleporting players | YES | Transform modification |
| Logging/printing | No | No entity interaction |
| Inside command execute() | YES | Commands run on ForkJoinPool |
commands need world.execute() too!
Command handlers run on
ForkJoinPool.commonPool(), not the world thread. If your command modifies entities, wrap it in world.execute().Common Patterns
Event Handler Pattern
1private void onPlayerConnect(PlayerConnectEvent event) {2 Player player = event.getPlayer();3 World world = player.getWorld();4 5 // Safe: packet sending doesn't need execute()6 showWelcomeTitle(player);7 8 // Unsafe operations go in execute()9 world.execute(() -> {10 setupPlayerComponents(player);11 giveStarterItems(player);12 });13}1415private void showWelcomeTitle(Player player) {16 // Packets are thread-safe17 player.getPlayerConnection().write(18 new ShowEventTitle(...)19 );20}2122private void setupPlayerComponents(Player player) {23 // Component modifications - must be in execute()24 Ref ref = player.getReference(); 25 // ... modify components26}Delayed Execution
1// Execute after a delay (still on World thread)2world.execute(() -> {3 // First action4 player.setModel("Ghost");5 6 // Schedule another action7 world.schedule(20, () -> { // 20 ticks = 1 second8 player.resetModel();9 });10});Batch Operations
1// Multiple players - batch in single execute2World world = getUniverse().getWorlds().get(0);34world.execute(() -> {5 for (Player player : world.getPlayers()) {6 player.setSpeed(2.0f);7 player.sendMessage("Speed boost activated!");8 }9});Debugging Thread Issues
If you see these errors, you likely have a threading issue:
1// Common error messages2java.lang.IllegalStateException: Not on world thread3java.util.ConcurrentModificationException4NullPointerException in component access (silent failure)silent failures
Some threading violations don't crash - they just silently fail. If your code "doesn't work" but produces no errors, check your threading!
Quick Reference
ThreadSafePlugin.javajava
1// Template for thread-safe event handling2public class ThreadSafePlugin extends JavaPlugin {3 4 @Override5 protected void start() {6 getEventRegistry().register(7 PlayerConnectEvent.class,8 this::handlePlayerConnect9 );10 }11 12 private void handlePlayerConnect(PlayerConnectEvent event) {13 Player player = event.getPlayer();14 15 // Step 1: Read-only operations (safe anywhere)16 String name = player.getName();17 UUID uuid = player.getUuid();18 19 // Step 2: Packet operations (safe anywhere)20 sendWelcome(player);21 22 // Step 3: Entity modifications (MUST use execute)23 player.getWorld().execute(() -> {24 modifyPlayer(player);25 });26 }27 28 private void sendWelcome(Player player) {29 // Thread-safe: packet sending30 player.getPlayerConnection().write(...);31 }32 33 private void modifyPlayer(Player player) {34 // Only call from world.execute()!35 player.setModel("Mouse");36 player.setSpeed(1.5f);37 }38}