Minecraft anticheat guide
FairPlay API: Flag Events and Check Info for Plugin Developers
Handle FairPlay flag events, read check info and violation levels, and pick between post-record notifications and pre-store vetoes in your Bukkit plugin.
Short answer: which FairPlay hook to use
Use FairPlayFlagEvent for post-record notifications, such as Discord logs, metrics, or staff dashboards. It is synchronous and cannot be cancelled. Use the violation event bus only when you need to veto a detection before FairPlay stores it. Per FairPlay engineering notes, copy event fields before you hand them to another thread, and keep Bukkit work in the owner context.
Pick a flag event or the violation bus
The two hooks run at different points in the detection path. Choosing the wrong one is the most common mistake in new integrations, because a notification listener cannot change the outcome of a detection.
A cancellation through the violation bus does not undo packet cancellation, setback, or enforcement that a check already performed before it called the violation pipeline.
| Need | Hook | Can change the outcome? | Thread context |
|---|---|---|---|
| Log or count a stored detection | FairPlayFlagEvent | No | Player's entity owner context, including Folia |
| Veto a detection before storage | FairPlayViolationListener through getEventBus().onViolation | Yes, by returning true | FairPlay detection thread, often PacketEvents or Netty |
| Read check list or violation levels | FairPlayService methods | No | Your calling thread, with the service ready |
Read the flag event fields correctly
The event exposes detection-time values. Most getters are immutable snapshots, so you can store them safely. Two getters need extra care. getPlayer() returns an Optional that is valid only during the dispatch, and the event is dropped if the player disconnects before dispatch.
The current violation level is the post-cap, post-expiry total that FairPlay stored. The maximum is the highest configured punishment tier for that check. It is not a universal ban limit, so do not hard-code it as one.
| Getter | Use it for |
|---|---|
| getPlayerId() and getPlayerName() | Stable identity for logs and dedupe keys |
| getCheckName() and getCheckType() | Grouping by check and by check family |
| getAddedViolationLevel() | Summing what one detection contributed |
| getCurrentViolationLevel() | The stored total after this detection |
| getMaxViolationLevel() | An OptionalDouble for the highest configured tier, when present |
| getDebug() and getTimestampMillis() | Staff-facing detail and ordering |
| getPlayer() | Optional live Player, only inside the dispatch |
A minimal flag listener
This listener is copied from the FairPlay API notes. It only reads two getters, which is enough to start. Add your own storage or messaging logic in the handler body only after you have copied the values you need.
import com.arkflame.fairplay.api.event.FairPlayFlagEvent;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
public final class FairPlayListener implements Listener {
@EventHandler
public void onFairPlayFlag(FairPlayFlagEvent event) {
String check = event.getCheckName();
double recordedVl = event.getCurrentViolationLevel();
}
}Handle flags without blocking detection
A flag handler runs on the path where detections are recorded. Keep it short, and move slow work away from it. The steps below cover the usual pattern for webhooks and staff logs.
Read check info and violation levels
Use the service for lookups that do not need an event. Keep these calls on the main thread unless you have confirmed that your use is safe elsewhere. The example below is copied from the FairPlay API notes.
Reset calls change player state. Gate them behind a staff command, and log the actor and the reason.
Collection<FairPlayCheckInfo> checks = api.getChecks();
Optional<FairPlayCheckInfo> check = api.getCheck("killaura");
double vl = api.getViolationLevel(playerId, "killaura");
Map<String, Double> all = api.getViolationLevels(playerId);Check the dependency and install setup
Your plugin should reference FairPlay only at compile time. Install the FairPlay plugin JAR on the server, and do not install or shade the API JAR into your own plugin. Use a hard dependency when your listener references API classes directly, and a soft dependency only when every API touch is guarded by a presence check.
depend: [FairPlay]What we have not verified
The API notes describe the artifact as 4.1.1, while the public site documents 4.0.5. Compile against the artifact that matches the FairPlay build on your server, and confirm the version with the service's version method at runtime.
Folia dispatch behavior is described in the notes but is runtime-evidence-pending. Test a flag listener on a Folia staging server before relying on it, and confirm that it stays in the player's entity context.
FAQ
Can I cancel a FairPlay flag event?
No. FairPlayFlagEvent is a notification. If you need to drop a detection before storage, use the violation event bus and return true from the listener.
Why does getPlayer() sometimes return an empty Optional?
The player may have disconnected before dispatch, or you may be reading it outside the dispatch window. Use getPlayerId() and getPlayerName() for anything you store.
Is getMaxViolationLevel() the ban limit?
No. It is the highest configured punishment tier for that check, when one is present. Treat it as configuration context, not a universal limit.
Which value should I sum across flags?
Sum getAddedViolationLevel() when you want what each detection contributed. Use getCurrentViolationLevel() when you want the stored total at that moment.