Events API
Paper extends Bukkit’s event system with numerous additional events and enhancements. Events allow plugins to listen to and modify game behavior.Event Basics
Listening to Events
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
public class MyListener implements Listener {
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
player.sendMessage("Welcome to the server!");
}
}
Registering Listeners
public class MyPlugin extends JavaPlugin {
@Override
public void onEnable() {
// Register listener
getServer().getPluginManager().registerEvents(new MyListener(), this);
// Or use this shorthand
Bukkit.getPluginManager().registerEvents(new MyListener(), this);
}
}
Event Priority
Control when your listener is called relative to other plugins:import org.bukkit.event.EventPriority;
@EventHandler(priority = EventPriority.LOWEST)
public void onEarliest(PlayerJoinEvent event) {
// Called first
}
@EventHandler(priority = EventPriority.LOW)
public void onEarly(PlayerJoinEvent event) { }
@EventHandler(priority = EventPriority.NORMAL) // Default
public void onNormal(PlayerJoinEvent event) { }
@EventHandler(priority = EventPriority.HIGH)
public void onLate(PlayerJoinEvent event) { }
@EventHandler(priority = EventPriority.HIGHEST)
public void onLatest(PlayerJoinEvent event) { }
@EventHandler(priority = EventPriority.MONITOR)
public void onMonitor(PlayerJoinEvent event) {
// Called last, should not modify event
// Used for logging/monitoring only
}
Use
MONITOR priority only for observing events, never for modifying them.Ignoring Cancelled Events
@EventHandler(ignoreCancelled = true)
public void onBlockBreak(BlockBreakEvent event) {
// Not called if event is cancelled by another plugin
}
Player Events
Join/Quit Events
@EventHandler
public void onJoin(PlayerJoinEvent event) {
Player player = event.getPlayer();
// Customize join message
event.joinMessage(Component.text("Welcome " + player.getName()));
// Or hide join message
event.joinMessage(null);
}
@EventHandler
public void onQuit(PlayerQuitEvent event) {
Player player = event.getPlayer();
// Customize quit message
event.quitMessage(Component.text(player.getName() + " left"));
}
Movement Events
@EventHandler
public void onMove(PlayerMoveEvent event) {
Location from = event.getFrom();
Location to = event.getTo();
// Check if player changed block
if (from.getBlockX() != to.getBlockX()
|| from.getBlockY() != to.getBlockY()
|| from.getBlockZ() != to.getBlockZ()) {
// Player moved to different block
}
// Cancel movement
event.setCancelled(true);
// Or modify destination
event.setTo(newLocation);
}
PlayerMoveEvent is called very frequently. Avoid heavy operations in this event.
Interaction Events
@EventHandler
public void onInteract(PlayerInteractEvent event) {
Player player = event.getPlayer();
Action action = event.getAction();
Block block = event.getClickedBlock();
ItemStack item = event.getItem();
if (action == Action.RIGHT_CLICK_BLOCK) {
if (block != null && block.getType() == Material.CHEST) {
event.setCancelled(true);
player.sendMessage("You cannot open this chest!");
}
}
}
@EventHandler
public void onInteractEntity(PlayerInteractEntityEvent event) {
Player player = event.getPlayer();
Entity entity = event.getRightClicked();
if (entity instanceof Villager) {
event.setCancelled(true);
player.sendMessage("Trading is disabled!");
}
}
Chat Events
import io.papermc.paper.event.player.AsyncChatEvent;
@EventHandler
public void onChat(AsyncChatEvent event) {
Player player = event.getPlayer();
Component message = event.message();
// Modify message
event.message(Component.text("[Chat] ").append(message));
// Cancel message
event.setCancelled(true);
// Custom recipients
event.viewers().clear();
event.viewers().addAll(Bukkit.getOnlinePlayers());
}
AsyncChatEvent is asynchronous. Be careful with thread safety!Block Events
Block Break/Place
@EventHandler
public void onBlockBreak(BlockBreakEvent event) {
Player player = event.getPlayer();
Block block = event.getBlock();
// Check permission
if (!player.hasPermission("myplugin.break." + block.getType())) {
event.setCancelled(true);
player.sendMessage("You cannot break this block!");
return;
}
// Modify drops
event.setDropItems(false);
block.getWorld().dropItemNaturally(block.getLocation(), new ItemStack(Material.DIAMOND));
// Modify experience
event.setExpToDrop(100);
}
@EventHandler
public void onBlockPlace(BlockPlaceEvent event) {
Player player = event.getPlayer();
Block block = event.getBlock();
// Cancel placement
if (block.getY() > 100) {
event.setCancelled(true);
player.sendMessage("Cannot place blocks above Y=100");
}
}
Block Growth
@EventHandler
public void onGrow(BlockGrowEvent event) {
Block block = event.getBlock();
// Cancel growth in certain worlds
if (block.getWorld().getName().equals("lobby")) {
event.setCancelled(true);
}
}
@EventHandler
public void onSpread(BlockSpreadEvent event) {
// Fire spread, grass spread, etc.
if (event.getSource().getType() == Material.FIRE) {
event.setCancelled(true);
}
}
Entity Events
Entity Damage
@EventHandler
public void onDamage(EntityDamageEvent event) {
Entity entity = event.getEntity();
double damage = event.getDamage();
EntityDamageEvent.DamageCause cause = event.getCause();
// Cancel fall damage
if (cause == EntityDamageEvent.DamageCause.FALL) {
event.setCancelled(true);
}
// Modify damage
event.setDamage(damage * 2);
}
@EventHandler
public void onEntityDamageByEntity(EntityDamageByEntityEvent event) {
Entity damager = event.getDamager();
Entity victim = event.getEntity();
if (damager instanceof Player player) {
if (victim instanceof Player) {
// PvP damage
event.setCancelled(true);
player.sendMessage("PvP is disabled!");
}
}
}
Entity Death
@EventHandler
public void onDeath(EntityDeathEvent event) {
LivingEntity entity = event.getEntity();
Player killer = entity.getKiller();
// Modify drops
event.getDrops().clear();
event.getDrops().add(new ItemStack(Material.DIAMOND, 5));
// Modify experience
event.setDroppedExp(100);
if (killer != null) {
killer.sendMessage("You killed a " + entity.getType());
}
}
@EventHandler
public void onPlayerDeath(PlayerDeathEvent event) {
Player player = event.getPlayer();
// Custom death message
event.deathMessage(Component.text(player.getName() + " died!"));
// Keep inventory
event.setKeepInventory(true);
event.setKeepLevel(true);
event.getDrops().clear();
}
Entity Spawn
@EventHandler
public void onSpawn(EntitySpawnEvent event) {
Entity entity = event.getEntity();
// Cancel zombie spawning
if (entity.getType() == EntityType.ZOMBIE) {
event.setCancelled(true);
}
}
@EventHandler
public void onCreatureSpawn(CreatureSpawnEvent event) {
LivingEntity entity = event.getEntity();
CreatureSpawnEvent.SpawnReason reason = event.getSpawnReason();
// Allow natural spawns only
if (reason != CreatureSpawnEvent.SpawnReason.NATURAL) {
event.setCancelled(true);
}
}
Inventory Events
Inventory Click
@EventHandler
public void onClick(InventoryClickEvent event) {
Player player = (Player) event.getWhoClicked();
Inventory inventory = event.getInventory();
ItemStack clicked = event.getCurrentItem();
int slot = event.getSlot();
ClickType clickType = event.getClick();
// Cancel clicking in custom GUI
if (inventory.getHolder() instanceof MyCustomHolder) {
event.setCancelled(true);
// Handle click
if (clicked != null && clicked.getType() == Material.DIAMOND) {
player.sendMessage("You clicked a diamond!");
}
}
}
Inventory Open/Close
@EventHandler
public void onOpen(InventoryOpenEvent event) {
Player player = (Player) event.getPlayer();
Inventory inventory = event.getInventory();
// Cancel opening certain inventories
if (inventory.getType() == InventoryType.ENCHANTING) {
event.setCancelled(true);
player.sendMessage("Enchanting is disabled!");
}
}
@EventHandler
public void onClose(InventoryCloseEvent event) {
Player player = (Player) event.getPlayer();
// Cleanup when GUI is closed
}
World Events
Chunk Events
@EventHandler
public void onChunkLoad(ChunkLoadEvent event) {
Chunk chunk = event.getChunk();
World world = chunk.getWorld();
// Process newly loaded chunk
}
@EventHandler
public void onChunkUnload(ChunkUnloadEvent event) {
Chunk chunk = event.getChunk();
// Cancel unloading
// event.setCancelled(true); // Not available in all events
}
Weather Events
@EventHandler
public void onWeatherChange(WeatherChangeEvent event) {
World world = event.getWorld();
boolean raining = event.toWeatherState();
// Cancel rain
if (raining) {
event.setCancelled(true);
}
}
Paper-Specific Events
Paper adds many additional events in theio.papermc.paper.event package:
Player Events
import io.papermc.paper.event.player.*;
@EventHandler
public void onPreLogin(AsyncPlayerPreLoginEvent event) {
String name = event.getName();
UUID uuid = event.getUniqueId();
InetAddress address = event.getAddress();
// Async whitelist check, database lookup, etc.
if (!isWhitelisted(uuid)) {
event.disallow(AsyncPlayerPreLoginEvent.Result.KICK_WHITELIST,
Component.text("You are not whitelisted!"));
}
}
@EventHandler
public void onPlayerItemFrameChange(PlayerItemFrameChangeEvent event) {
Player player = event.getPlayer();
ItemFrame frame = event.getItemFrame();
ItemStack item = event.getItemStack();
// Cancel item frame interaction
event.setCancelled(true);
}
Entity Events
import io.papermc.paper.event.entity.*;
@EventHandler
public void onEntityMoveEvent(EntityMoveEvent event) {
// More efficient than PlayerMoveEvent for entities
Entity entity = event.getEntity();
Location from = event.getFrom();
Location to = event.getTo();
}
Custom Events
Create your own events:import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
public class CustomEvent extends Event {
private static final HandlerList HANDLERS = new HandlerList();
private final Player player;
private String message;
public CustomEvent(Player player, String message) {
this.player = player;
this.message = message;
}
public Player getPlayer() {
return player;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@Override
public HandlerList getHandlers() {
return HANDLERS;
}
public static HandlerList getHandlerList() {
return HANDLERS;
}
}
Calling Custom Events
// Create and call event
CustomEvent event = new CustomEvent(player, "Hello");
Bukkit.getPluginManager().callEvent(event);
// Check if modified
String message = event.getMessage();
Cancellable Events
import org.bukkit.event.Cancellable;
public class CancellableCustomEvent extends Event implements Cancellable {
private static final HandlerList HANDLERS = new HandlerList();
private boolean cancelled = false;
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public void setCancelled(boolean cancel) {
this.cancelled = cancel;
}
@Override
public HandlerList getHandlers() {
return HANDLERS;
}
public static HandlerList getHandlerList() {
return HANDLERS;
}
}
Event Best Practices
Always check if an event is cancellable before calling
setCancelled().Async events (like
AsyncChatEvent, AsyncPlayerPreLoginEvent) run off the main thread. Do not access Bukkit API from async events!Use
@EventHandler(ignoreCancelled = true) to avoid processing cancelled events unnecessarily.Complete Event List
For a complete list of all available events, see:- Paper Javadocs - Events
- Bukkit:
org.bukkit.event.* - Paper:
io.papermc.paper.event.*
Next Steps
Plugin Development
Build plugins with events
Entity API
Work with entities