Paper Plugin Guide
File mode0

Paper essentials

Events and listeners

React to things that happen in the game: joins, block breaks, damage, chat and hundreds more.

Beginner28 min read

Almost every plugin reacts to something players do. On this page you will write a listener that greets players when they join, stop players from mining diamond ore, change death messages, and learn how to find the right event for any idea.

What is an event?

A Minecraft server is busy. Every second, players join, walk, break blocks, open chests, chat and fight. Each time one of these things happens, Paper creates an event: a small Java object that describes what is happening right now. Who joined? Which block is breaking? How much damage is the zombie about to take?

Before Paper finishes the action, it hands that event to every plugin that asked to hear about it. Your plugin can read the details, change some of them, or even stop the action completely.

How an event reaches your plugin A player breaks a block. Paper creates a BlockBreakEvent and hands it to every listener in priority order. If any listener cancels it, the block stays; otherwise the block breaks. A player breaks a block something happens in the game Paper creates an event BlockBreakEvent Listeners run one at a time, lowest priority first Plugin A EventPriority.LOW checks a rule Plugin B EventPriority.NORMAL does nothing Plugin C EventPriority.HIGH setCancelled(true) Was the event canceled? yes no The block stays nothing changes The block breaks and drops its items Canceling is how a plugin says no
A player breaks a block. Paper creates a BlockBreakEvent, gives every listener a turn, and then breaks the block unless a listener canceled the event.

Three words come up again and again on this page:

  • Event: the thing that happened, as an object you can ask questions, such as PlayerJoinEvent or BlockBreakEvent.
  • Listener: a class you write that wants to hear about events.
  • Event handler: a method inside your listener that Paper calls when the event happens.

Your first listener

Here is the whole plan for a plugin that says hello to every player who joins. It needs exactly three pieces:

  1. Write a class that implements Listener

    This tells Paper "this class contains event handlers". Listener is an interface with no methods in it; it works like a label you stick on the class.

  2. Add a method marked with @EventHandler

    The method takes one parameter: the event you care about. Paper looks at that parameter's type to decide when to call the method.

  3. Register the listener when the plugin starts

    In your main class's onEnable method, hand an object of your listener class to Paper's plugin manager. Until you do this, Paper does not know your listener exists.

This is the listener. Read the notes under the code, then hover any word in the code to see what it is.

WelcomeListener.javaCompiles on Paper 26.3Compile Lab
Where this file livesevents-first-listenersrcmainjavacomexampleeventsfirstlistenerWelcomeListener.java

The package com.example.eventsfirstlistener is the folder path com/example/eventsfirstlistener inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • events-first-listener/
    • src/main/
      • java/com/example/eventsfirstlistener/Package com.example.eventsfirstlistener
        • FirstListenerPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • WelcomeListener.javayou are hereListener: reacts to events
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

1package com.example.eventsfirstlistener;2 3import org.bukkit.entity.Player;4import org.bukkit.event.EventHandler;5import org.bukkit.event.Listener;6import org.bukkit.event.player.PlayerJoinEvent;7 8public final class WelcomeListener implements Listener {9 10    @EventHandler11    public void onJoin(PlayerJoinEvent event) {12        Player player = event.getPlayer();13        player.sendRichMessage("<green>Welcome to the server!");14    }15}
  1. The package is the folder path of this file. It must match the folders under src/main/java.
  2. Imports tell Java which classes you mean. IntelliJ adds them for you when you press Alt+Enter on a red name.
  3. This class promises to be a listener. Without implements Listener, Paper refuses to register it.
  4. An annotation. It marks the next method as an event handler. Paper ignores methods without it.
  5. The method name can be anything you like; onJoin just describes it. What matters is the parameter type, PlayerJoinEvent, which means "call me whenever a player joins".
  6. Every player event knows which player it is about. This saves that player in a variable called player.
  7. Sends a chat message to this one player. The text uses MiniMessage tags, so <green> makes it green.

And this is the plugin's main class, which registers the listener when the plugin is enabled:

FirstListenerPlugin.javaCompiles on Paper 26.3Compile Lab
Where this file livesevents-first-listenersrcmainjavacomexampleeventsfirstlistenerFirstListenerPlugin.java

The package com.example.eventsfirstlistener is the folder path com/example/eventsfirstlistener inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • events-first-listener/
    • src/main/
      • java/com/example/eventsfirstlistener/Package com.example.eventsfirstlistener
        • FirstListenerPlugin.javayou are hereMain class: Paper starts here (named as main in plugin.yml)
        • WelcomeListener.javaListener: reacts to events
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

1package com.example.eventsfirstlistener;2 3import org.bukkit.plugin.java.JavaPlugin;4 5public final class FirstListenerPlugin extends JavaPlugin {6 7    @Override8    public void onEnable() {9        getServer().getPluginManager().registerEvents(new WelcomeListener(), this);10    }11}
  1. Your main class. Paper creates it when the server starts, because plugin.yml names it as main.
  2. Paper calls onEnable once, when your plugin starts. This is where you set everything up.
  3. Registers the listener. The first argument is a new WelcomeListener object; the second, this, is your plugin, so Paper knows who owns the listener.

Build the plugin, start your test server with Run Paper Server (see Your first plugin), and join with localhost. As soon as you arrive, you see this in chat:

Steve joined the game
Welcome to the server!

The yellow line is Minecraft's normal join message, which goes to everyone. The green line is yours, and only the player who joined sees it, because you sent it to player.

How Paper finds your handler

When your plugin calls registerEvents, Paper looks through your listener class for methods to call later. A method becomes an event handler when all of these are true:

  • It has the @EventHandler annotation directly above it.
  • It has exactly one parameter.
  • That parameter's type is an event class, such as PlayerJoinEvent.

Handlers are public by convention. Paper also accepts private methods, but every example you will read online uses public, so stick with that. The return type is always void, because Paper does nothing with a returned value.

If a handler has the wrong shape, for example a parameter that is a Player instead of an event, Paper skips it and prints an error when your plugin starts:

Server console
[14:02:11 INFO]: [FirstListener] Enabling FirstListener v1.0.0[14:02:11 ERROR]: [FirstListener] FirstListener v1.0.0 attempted to register an invalid EventHandler method signature "public void com.example.eventsfirstlistener.WelcomeListener.onJoin(org.bukkit.entity.Player)" in class com.example.eventsfirstlistener.WelcomeListener

Getting information from the event

An event object is full of useful methods. A BlockBreakEvent knows the block and the player; an EntityDamageEvent knows the entity, the damage amount and the cause. The fastest way to explore is IntelliJ's autocomplete: type event. inside your handler and look at the list.

Type a dot after event and IntelliJ lists everything the event can tell you. Use the arrow keys to read the descriptions.

These are the events beginners use most. Each one lives in a package you need to import; IntelliJ does that for you.

EventFires whenCan you cancel it?
PlayerJoinEventA player finishes joining the server.No
PlayerQuitEventA player leaves the server.No
AsyncChatEventA player sends a chat message (runs off the main thread).Yes
PlayerInteractEventA player left-clicks or right-clicks the air or a block, or steps on a pressure plate.Yes
PlayerMoveEventA player moves or turns their head. It fires very often.Yes
BlockBreakEventA player breaks a block.Yes
BlockPlaceEventA player places a block.Yes
EntityDamageEventAny entity, including a player, is about to take damage.Yes
PlayerDeathEventA player dies.Yes
InventoryClickEventA player clicks a slot in any open inventory.Yes
PlayerDropItemEventA player drops an item.Yes

Paper 26.3 has 465 event classes. You do not need to memorize them: the Event Finder lets you search them in plain English ("when a player opens a chest") and gives you listener code to copy.

A bigger example: join and quit messages

Events do more than tell you what happened. Many of them let you change the outcome. PlayerJoinEvent lets you replace the yellow "joined the game" message that everyone sees, and PlayerQuitEvent does the same for leaving.

JoinQuitListener.javaCompiles on Paper 26.3Compile Lab
Where this file livesevents-demosrcmainjavacomexampleeventsdemoJoinQuitListener.java

The package com.example.eventsdemo is the folder path com/example/eventsdemo inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • events-demo/
    • src/main/
      • java/com/example/eventsdemo/Package com.example.eventsdemo
        • DeathListener.javaListener: reacts to events
        • EventsDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • JoinQuitListener.javayou are hereListener: reacts to events
        • ProtectionListener.javaListener: reacts to events
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

1package com.example.eventsdemo;2 3import java.time.Duration;4import net.kyori.adventure.text.minimessage.MiniMessage;5import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;6import net.kyori.adventure.title.Title;7import org.bukkit.entity.Player;8import org.bukkit.event.EventHandler;9import org.bukkit.event.Listener;10import org.bukkit.event.player.PlayerJoinEvent;11import org.bukkit.event.player.PlayerQuitEvent;12 13public final class JoinQuitListener implements Listener {14 15    private static final MiniMessage MINI_MESSAGE = MiniMessage.miniMessage();16 17    @EventHandler18    public void onJoin(PlayerJoinEvent event) {19        Player player = event.getPlayer();20        event.joinMessage(MINI_MESSAGE.deserialize("<gray>[<green>+</green>] <name>",21            Placeholder.unparsed("name", player.getName())));22        player.showTitle(Title.title(23            MINI_MESSAGE.deserialize("<gold>Welcome!"),24            MINI_MESSAGE.deserialize("<gray>Have fun, <name>", Placeholder.unparsed("name", player.getName())),25            Title.Times.times(Duration.ofMillis(500), Duration.ofSeconds(3), Duration.ofMillis(500))));26    }27 28    @EventHandler29    public void onQuit(PlayerQuitEvent event) {30        event.quitMessage(MINI_MESSAGE.deserialize("<gray>[<red>-</red>] <name>",31            Placeholder.unparsed("name", event.getPlayer().getName())));32    }33}
  1. One shared MiniMessage object turns tag text such as <gray> into colored chat components. static final means there is exactly one, and it never changes.
  2. Replaces the join message that every online player sees. The method takes a Component, which is Paper's type for formatted text.
  3. Fills the <name> tag with the player's name. "Unparsed" means the name is inserted as plain text, so it can never inject formatting tags.
  4. Shows a big title in the middle of this player's screen, with a smaller subtitle underneath.
  5. How long the title fades in, stays, and fades out.
  6. A second handler in the same class. One listener can hold as many handlers as you like.

Here is what everyone sees when Steve joins, and what Steve sees on his screen:

[+] Steve
Welcome! Have fun, Steve

Canceling events

Some events can be stopped. They implement an interface called Cancellable (the API spells it with two l's), and they have a method setCancelled(true). When a listener cancels an event, Paper does not finish the action: the block does not break, the damage is not dealt, the message is not sent.

This listener protects diamond ore. Players without a special permission cannot mine it:

ProtectionListener.javaCompiles on Paper 26.3Compile Lab
Where this file livesevents-demosrcmainjavacomexampleeventsdemoProtectionListener.java

The package com.example.eventsdemo is the folder path com/example/eventsdemo inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • events-demo/
    • src/main/
      • java/com/example/eventsdemo/Package com.example.eventsdemo
        • DeathListener.javaListener: reacts to events
        • EventsDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • JoinQuitListener.javaListener: reacts to events
        • ProtectionListener.javayou are hereListener: reacts to events
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

1package com.example.eventsdemo;2 3import org.bukkit.Material;4import org.bukkit.block.Block;5import org.bukkit.entity.Player;6import org.bukkit.event.EventHandler;7import org.bukkit.event.Listener;8import org.bukkit.event.block.BlockBreakEvent;9 10public final class ProtectionListener implements Listener {11 12    @EventHandler13    public void onBreak(BlockBreakEvent event) {14        Block block = event.getBlock();15        Player player = event.getPlayer();16        boolean isDiamondOre = block.getType() == Material.DIAMOND_ORE17            || block.getType() == Material.DEEPSLATE_DIAMOND_ORE;18        if (!isDiamondOre) {19            return;20        }21        if (player.hasPermission("eventsdemo.mine.diamonds")) {22            return;23        }24        event.setCancelled(true);25        player.sendRichMessage("<red>You need permission to mine diamond ore here.");26    }27}
  1. Called every time any player breaks any block.
  2. Checks which block it is. Material lists every block and item type. Diamond ore has two versions: normal stone ore and deepslate ore, so the code checks both.
  3. If the block is not diamond ore, this handler has nothing to do, so it stops right away with return. This style is called a guard clause.
  4. Players with the permission are allowed through. Server owners give permissions with a permissions plugin such as LuckPerms.
  5. Cancels the event. The ore stays exactly where it was, and nothing drops.
  6. Always tell players why something did not work, or they will think the server is broken.

The permission is declared in plugin.yml, so Paper knows who has it by default:

plugin.ymlCompiles on Paper 26.3Compile Lab
Where this file livesevents-demosrcmainresourcesplugin.yml

Files in src/main/resources are copied into the jar exactly as they are. Paper looks for plugin.yml at the top of the jar.

  • events-demo/
    • src/main/
      • java/com/example/eventsdemo/Package com.example.eventsdemo
        • DeathListener.javaListener: reacts to events
        • EventsDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • JoinQuitListener.javaListener: reacts to events
        • ProtectionListener.javaListener: reacts to events
      • resources/Files copied into the jar as they are
        • plugin.ymlyou are hereTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

1name: EventsDemo2version: '1.0.0'3main: com.example.eventsdemo.EventsDemoPlugin4api-version: '26.3'5description: Join and quit messages, a welcome title, diamond ore protection and custom death messages.6permissions:7  eventsdemo.mine.diamonds:8    description: Lets a player mine diamond ore.9    default: op
  1. The permission's name. Starting it with your plugin's name keeps it from clashing with other plugins.
  2. Server operators have it automatically; everyone else needs it granted.
You need permission to mine diamond ore here.

Not every event can be canceled. PlayerJoinEvent cannot, because by the time it fires the player is already in the world. To stop someone from joining, plugins listen to AsyncPlayerPreLoginEvent instead, which fires while the player is still connecting. The Event Finder shows a "Cancelable" badge on every event that can be canceled.

Changing death messages

PlayerDeathEvent fires when a player dies, and lets you replace the death message. This handler shows who killed whom, or a different line when no player was involved:

DeathListener.javaCompiles on Paper 26.3Compile Lab
Where this file livesevents-demosrcmainjavacomexampleeventsdemoDeathListener.java

The package com.example.eventsdemo is the folder path com/example/eventsdemo inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • events-demo/
    • src/main/
      • java/com/example/eventsdemo/Package com.example.eventsdemo
        • DeathListener.javayou are hereListener: reacts to events
        • EventsDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • JoinQuitListener.javaListener: reacts to events
        • ProtectionListener.javaListener: reacts to events
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

1package com.example.eventsdemo;2 3import net.kyori.adventure.text.minimessage.MiniMessage;4import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;5import org.bukkit.entity.Player;6import org.bukkit.event.EventHandler;7import org.bukkit.event.Listener;8import org.bukkit.event.entity.PlayerDeathEvent;9 10public final class DeathListener implements Listener {11 12    private static final MiniMessage MINI_MESSAGE = MiniMessage.miniMessage();13 14    @EventHandler15    public void onDeath(PlayerDeathEvent event) {16        Player player = event.getPlayer();17        Player killer = player.getKiller();18        if (killer == null) {19            event.deathMessage(MINI_MESSAGE.deserialize("<gray><name> ran out of luck.",20                Placeholder.unparsed("name", player.getName())));21            return;22        }23        event.deathMessage(MINI_MESSAGE.deserialize("<red><killer></red> <gray>defeated</gray> <red><name>",24            Placeholder.unparsed("killer", killer.getName()),25            Placeholder.unparsed("name", player.getName())));26    }27}
  1. The player who landed the final hit. If a creeper, lava or a fall killed the player, there is no killer and this is null.
  2. Always check for null before using the killer, or your handler crashes with a NullPointerException the first time someone falls off a cliff.
  3. Two placeholders in one message: one for the killer, one for the player who died.
Alex defeated Steve
Steve ran out of luck.

One plugin, many listeners

Real plugins split their handlers into several listener classes, one per topic, and register all of them in onEnable:

EventsDemoPlugin.javaCompiles on Paper 26.3Compile Lab
Where this file livesevents-demosrcmainjavacomexampleeventsdemoEventsDemoPlugin.java

The package com.example.eventsdemo is the folder path com/example/eventsdemo inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • events-demo/
    • src/main/
      • java/com/example/eventsdemo/Package com.example.eventsdemo
        • DeathListener.javaListener: reacts to events
        • EventsDemoPlugin.javayou are hereMain class: Paper starts here (named as main in plugin.yml)
        • JoinQuitListener.javaListener: reacts to events
        • ProtectionListener.javaListener: reacts to events
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

1package com.example.eventsdemo;2 3import org.bukkit.plugin.PluginManager;4import org.bukkit.plugin.java.JavaPlugin;5 6public final class EventsDemoPlugin extends JavaPlugin {7 8    @Override9    public void onEnable() {10        PluginManager pluginManager = getServer().getPluginManager();11        pluginManager.registerEvents(new JoinQuitListener(), this);12        pluginManager.registerEvents(new ProtectionListener(), this);13        pluginManager.registerEvents(new DeathListener(), this);14        getLogger().info("Listening for joins, quits, block breaks and deaths.");15    }16}
  1. Saving the plugin manager in a variable avoids writing getServer().getPluginManager() three times.
  2. Each listener is registered once. Registering the same listener twice would make its handlers run twice per event.
  3. Prints a line in the server console, which helps you confirm the plugin started.
  • events-demo/
    • src/
      • main/
        • java/
          • com/example/eventsdemo/
            • EventsDemoPlugin.javaThe main class: registers the three listeners
            • JoinQuitListener.javaJoin and quit messages and the welcome title
            • ProtectionListener.javaStops diamond ore from being mined without permission
            • DeathListener.javaCustom death messages
        • resources/
          • plugin.ymlPlugin name, version, main class and the diamond permission

You can download this whole example as a Gradle project, open it in IntelliJ, and run it, or open it in the Compile Lab to change it and compile it right in your browser.

Mistakes to avoid

  • Forgetting to register the listener. Nothing happens and no error appears. Check onEnable first.
  • Forgetting @EventHandler. The method looks right but never runs.
  • Listening to a parent event that has no handler list, such as PlayerEvent. Paper refuses with "Unable to find handler list for event org.bukkit.event.player.PlayerEvent. Static getHandlerList method required!". Listen to the specific event instead.
  • Doing heavy work in PlayerMoveEvent. It fires many times per second for every player. Keep that handler tiny, or check whether the player actually changed blocks first.
  • Assuming something cannot be null. getKiller(), the clicked block in PlayerInteractEvent and many other values can be empty. Read the method's description (hover it in a code block) before using it.
  • Touching the world from AsyncChatEvent. Events whose names start with "Async" run on a different thread, where most of the Paper API is off limits. Threads, performance and lag explains why.
Try it

Welcome back

Change the welcome listener so new players see "Welcome to the server for the first time" and returning players see "Welcome back", each with their name.

Hint 1

Players have a method that says whether they have joined this server before. Type player.has and look at what autocomplete offers.

Hint 2

Use player.hasPlayedBefore() inside an if and send a different message in each branch.

Show the solution

hasPlayedBefore() returns true when this server already has saved data for the player. The name goes in with a placeholder, exactly like the join message example.

WelcomeBackListener.javaCompiles on Paper 26.3Compile Lab
Where this file livesevents-exercisesrcmainjavacomexampleeventsexerciseWelcomeBackListener.java

The package com.example.eventsexercise is the folder path com/example/eventsexercise inside src/main/java: every dot in the package name is one folder. IntelliJ creates these folders for you when you make a new package.

  • events-exercise/
    • src/main/
      • java/com/example/eventsexercise/Package com.example.eventsexercise
        • EventsExercisePlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • WelcomeBackListener.javayou are hereListener: reacts to events
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

1package com.example.eventsexercise;2 3import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;4import org.bukkit.entity.Player;5import org.bukkit.event.EventHandler;6import org.bukkit.event.Listener;7import org.bukkit.event.player.PlayerJoinEvent;8 9public final class WelcomeBackListener implements Listener {10 11    @EventHandler12    public void onJoin(PlayerJoinEvent event) {13        Player player = event.getPlayer();14        if (player.hasPlayedBefore()) {15            player.sendRichMessage("<green>Welcome back, <name>!", Placeholder.unparsed("name", player.getName()));16        } else {17            player.sendRichMessage("<gold>Welcome to the server for the first time, <name>!",18                Placeholder.unparsed("name", player.getName()));19        }20    }21}
  1. The two branches send two different messages.
Try it

Protect a second block

Extend ProtectionListener so players without the permission cannot break beacons either.

Hint

Beacons are Material.BEACON. Add another condition to isDiamondOre, and give the variable a better name while you are there.

Show the solution

Add the beacon check with || ("or") and rename the variable so it still describes what it holds:

ProtectionListener.java (changed lines)
1boolean isProtected = block.getType() == Material.DIAMOND_ORE2    || block.getType() == Material.DEEPSLATE_DIAMOND_ORE3    || block.getType() == Material.BEACON;4if (!isProtected) {5    return;6}

The message should change too, for example to "You need permission to break this block here."

Recap

  • An event is an object Paper creates when something happens in the game.
  • A listener is a class that implements Listener; its event handlers are methods with @EventHandler and one event parameter.
  • Register every listener in onEnable with getServer().getPluginManager().registerEvents(listener, this).
  • Events give you information (getPlayer(), getBlock()) and often let you change the outcome (joinMessage, deathMessage).
  • Cancelable events stop the action with setCancelled(true). Always tell the player why.
  • Use the Event Finder to discover the right event for an idea.

Quick quiz

  1. Your listener class is perfect, but nothing happens when players join. What is the most likely cause?

  2. How does Paper know that onBreak(BlockBreakEvent event) should run when a block breaks?

  3. What does event.setCancelled(true) do in a BlockBreakEvent handler?

  4. In a PlayerDeathEvent handler, why must you check player.getKiller() for null?

  5. Which event should a plugin use to stop a banned name from joining at all?

Next steps