Skip to main content

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 the io.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:

Next Steps

Plugin Development

Build plugins with events

Entity API

Work with entities

Build docs developers (and LLMs) love