DPC Zettelkasten Open in the explorer

Main Thread Safety

Bukkit state may only be read on the server's main thread, so anything doing I/O snapshots first and sends afterwards.

A Bukkit server runs game logic on one thread. Plugin data structures reachable from that logic are not thread-safe, and reading them from a background thread produces corruption that surfaces far from its cause.

Snapshot on, send off

DPC API Faction Sync is the clearest worked example in the codebase, and its class comment states the rule outright. The work is split in two:

  1. collectSnapshot() — runs on the main thread. It walks the faction service and the members lists, and returns an immutable SyncSnapshot.
  2. dispatchAsync() — safe from any thread. It serializes the snapshot and hands it to the JDK HttpClient, which does the I/O on its own pool.

The scheduled task is therefore registered with runTaskTimer — the main thread variant — even though its purpose is a network call. This looks backwards until you see the split: the expensive part is already asynchronous inside HttpClient, and scheduling the task asynchronously would only move the unsafe part off the main thread.

The general rule

The pattern generalises to anything the plugin does off-thread: collect an immutable snapshot on the main thread, then act on the snapshot elsewhere. Never hold a reference to live Bukkit or service state across a thread boundary.

Where it also shows up

Getting this wrong is not usually a crash — it is a rare, unreproducible inconsistency. That is why the constraint is written into a class comment rather than left implicit.

Sources

Linked from