# Add Listener

> Add an event listener to a Minecraft plugin — a Listener class with @EventHandler methods, wired into onEnable with registerEvents. Use this whenever the user wants to react to something happening in-game: on join/quit, block break/place, player interact, entity damage/death, inventory click, chat, respawn, or any Bukkit/Paper event. Handles priority, cancelled-event handling, and hot-event performance. Reads the target stack from .mcplugin/config.yml.

- Skill: `itamarb2010-jpg/add-listener` (Agent Skill)
- Install (CLI): `npx skillmds@latest add itamarb2010-jpg/add-listener`
- Raw SKILL.md: https://api.skillmd.com/api/skills/itamarb2010-jpg/add-listener/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: itamarb2010-jpg (https://skillmd.com/u/itamarb2010-jpg)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/itamarb2010-jpg/add-listener

---


# Add an event listener

Wire up a listener that reacts to a Bukkit/Paper event, matching the project's stack and
conventions. Follow the collaborative rule: propose the design, show the code, then write
after the user approves.

## Phase 1: Load context

- Read `.mcplugin/config.yml` for `platform`, `mc_version`, `api_version`, `package`,
  `main_class`, `plugin_name`. If it's missing, tell the user to run `/setup-platform` (and
  `/scaffold`) first.
- Read the event reference and pitfalls (relative to this skill; `Glob` as fallback):
  - `../../references/api/events.md` — event catalogue, handler shape, priority, Paper events
  - `../../references/pitfalls.md` — the "Events" and "Threading" sections
- Look at existing source to match style: `Glob` `src/main/java/**/*.java`, read the main class
  and any existing `Listener` classes (reuse one if it's the natural home for the new handler).

## Phase 2: Clarify what to listen for

Ask (AskUserQuestion or plain text) only what you can't infer from the argument/description:
- **Which event(s)** — confirm the exact class name (e.g. `PlayerJoinEvent`,
  `BlockBreakEvent`, `EntityDamageByEntityEvent`). If unsure the event exists in the target
  version, say so and verify against `events.md` rather than guessing. Common families to map
  the user's intent onto:
  - *Player lifecycle:* `PlayerJoinEvent`, `PlayerQuitEvent`, `PlayerRespawnEvent`,
    `PlayerDeathEvent`.
  - *World interaction:* `BlockBreakEvent`, `BlockPlaceEvent`, `PlayerInteractEvent`.
  - *Combat/damage:* `EntityDamageEvent`, `EntityDamageByEntityEvent`, `EntityDeathEvent`.
  - *Inventory/chat:* `InventoryClickEvent`, `AsyncPlayerChatEvent` (chat fires **async** —
    don't touch the world from it without hopping to the main thread; Paper has its own
    Component-based chat event, prefer it there).
- **What should happen** when it fires, and under what condition (filter by world, permission,
  block type, item, etc.).
- **Priority** — `LOWEST`→`HIGHEST` for ordering, or `MONITOR` to observe the final outcome
  (read-only; never mutate in a `MONITOR` handler).
- **Cancelled events** — should the handler skip events another plugin already cancelled?
  (`ignoreCancelled = true`), or should it *do* the cancelling (`event.setCancelled(true)`)?

## Phase 3: Choose the handling strategy

Decide from the stack and explain the pick briefly:
- **One handler vs. several** — group related handlers in a single `Listener` class; split only
  when concerns differ. Prefer adding to an existing listener over creating a new one.
- **Blocking work** (DB/HTTP/file I/O) inside the handler → don't do it inline. Run it async,
  then hop back to the main thread to touch the world/player. On Folia there is no main thread —
  use the region/entity schedulers (`../../references/api/scheduler.md`).
- **Text output** — on Paper prefer Adventure `Component` (e.g. `player.sendMessage(Component…)`)
  over legacy `String` messages; on Spigot fall back to `String`.
- **Attaching data** to the involved item/entity → use `PersistentDataContainer` + a reused
  `NamespacedKey`, not the deprecated metadata API.

## Phase 4: Implement

Generate, in the project's package:
1. A class implementing `org.bukkit.event.Listener` with one `@EventHandler` method per event:
   - Exactly one event parameter; annotation present (a missing `@EventHandler` silently no-ops).
   - `priority` / `ignoreCancelled` set as clarified.
   - Null-checks where the API can return null (`event.getClickedBlock()`, `ItemMeta`, etc.).
   - **No heavy work in hot events** — `PlayerMoveEvent`, `BlockPhysicsEvent`, entity ticking
     fire constantly; early-return when nothing relevant changed (e.g. same block position).
   - Cancel by calling `event.setCancelled(true)` when the intent is to *stop* the action —
     returning early from the handler does nothing on its own.
2. **Register it**: in the main class `onEnable`, call
   `getServer().getPluginManager().registerEvents(new YourListener(this), this);` (a listener
   does nothing until registered). Pass any dependencies the handler needs via the constructor.
   If the plugin holds per-player state built from these events, clear it in `onDisable` and on
   `PlayerQuitEvent` — key it by `UUID`, never by a retained `Player` object (memory leak).

Show the new/edited files (the listener class, the `onEnable` diff) and get approval before
writing.

## Phase 5: Verify + hand off

- Re-check the pitfalls: listener **registered** in `onEnable`, every handler has `@EventHandler`
  with a single correct event parameter, cancelled-event handling matches intent
  (`isCancelled` / `ignoreCancelled` vs. `setCancelled(true)`), `MONITOR` handlers don't mutate,
  no blocking or heavy work in hot/high-frequency events (async + main-thread hop where needed).
- Suggest next steps: "`/build` to compile, then `/run-server` to trigger the event in game."

Do not fabricate APIs for a Minecraft version newer than `../../references/api/VERSION.md`
documents — if unsure an event or method exists in the target version, say so and verify.

