Skip to content

Listening to Events

In the previous chapter, we finished our first ever plugin and even ran it on a test server! We learned about the JavaPlugin class, which our plugin’s main class extends and used the logger for the first time.

In this chapter we will learn about event listeners, how they work, and how you can register them.

Each event listener has to implement this interface in order to be accepted as a listener. The interface is declared as follows:

Listener.java
package org.bukkit.event;
/**
* Simple interface for tagging all EventListeners
*/
public interface Listener {}

As you can see, the Listener interface is empty — there are no methods to override. It primarily helps distinguish actual event listeners from just normal classes. In order to get the actual listening logic, we have to use the @EventHandler annotation on a method. This method has to have exactly 1 parameter which extends Event.


For a simple block break event listener, create a new file:

  • Directorymain
    • Directoryjava
      • Directorycom/learnpaperdev/beginner
        • BeginnerPlugin.java
        • BlockBreakListener.java
    • Directoryresources
      • paper-plugin.yml

And fill it with the following contents:

BlockBreakListener.java
package com.learnpaperdev.beginner;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
public final class BlockBreakListener implements Listener {
@EventHandler
void onBlockBreak(BlockBreakEvent event) {
// Your listener logic
}
}

There is no limit on how many event handlers you can have in a listener, but it is good practice to keep them separated by logic. Having just one listener for all of your event handlers can get quite messy really quickly.

Each event usually has methods to retrieve useful information about the event. For example, a BlockBreakEvent has a method to return the involved Block. You can see these just by checking either the JavaDocs or checking your tab completion inside IntelliJ.

For now, we will only be doing a simple logging operation. But for that we need the ComponentLogger, which can only be retrieved from the plugin’s main class. Luckily for us, we can just create a constructor and add our logger as a parameter, like this:

BlockBreakListener.java
8 collapsed lines
package com.learnpaperdev.beginner;
import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
import org.bukkit.block.Block;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.jspecify.annotations.NullMarked;
@NullMarked
public final class BlockBreakListener implements Listener {
private final ComponentLogger logger;
public BlockBreakListener(ComponentLogger logger) {
this.logger = logger;
}
@EventHandler
void onBlockBreak(BlockBreakEvent event) {
final Block block = event.getBlock();
logger.warn("The block {} was broken at coordinates {} {} {}",
block.getType(), block.getX(), block.getY(), block.getZ()
);
}
}

In case you are confused on the layout of the logger.warn call: You can declare a placeholder and pass the value afterwards. This is only the case for slf4j-type loggers, which the ComponentLogger extends.

Furthermore, you may be wondering about my usage of @NullMarked. In Java, each object can be null. This usually requires attention by the developer in order to make sure that a parameter is not actually null. But when you want to assume that each value passed is not null, you can annotate that parameter with @NonNull. In fact, IntelliJ itself suggests that in our event handler. We know that this method will always be called with a valid object, so there is no need to null checks. Same with our constructor. So we can just annotate the whole class as not null.

Now, in order to register our listener, we have to go back to our main class. In our onEnable method, we can add the following code to register our listener:

BeginnerPlugin.java
@Override
public void onEnable() {
// We use a PluginManager in order to register events
final PluginManager pluginManager = getServer().getPluginManager();
// Create our listener instance
final BlockBreakListener listener = new BlockBreakListener(getComponentLogger());
// #registerEvents requires the listener we want to register, and our plugin instance
pluginManager.registerEvents(listener, this);
getComponentLogger().warn("Our plugin has been enabled!");
}

When you join your server now (ip: localhost) and break a few blocks, you should see logging similar to this:

Terminal window
[11:38:17 WARN]: [BeginnerPlugin] The block GRASS_BLOCK was broken at coordinates -14 80 -26
[11:38:23 WARN]: [BeginnerPlugin] The block GRASS_BLOCK was broken at coordinates -13 80 -26
[11:38:24 WARN]: [BeginnerPlugin] The block GRASS_BLOCK was broken at coordinates -12 80 -26
[11:38:26 WARN]: [BeginnerPlugin] The block FERN was broken at coordinates -17 80 -22
[11:38:29 WARN]: [BeginnerPlugin] The block SPRUCE_LEAVES was broken at coordinates -16 80 -12
[11:38:30 WARN]: [BeginnerPlugin] The block SPRUCE_LEAVES was broken at coordinates -15 80 -11

Certain events can be cancelled. That means that certain expected side effects do not actually happen. All events which implement Cancellable can be cancelled. Our BlockBreakEvent is one of those cancellable events. That means that you can run event.setCancelled(boolean) to disallow the block from being broken.

As an example for that, we can modify our event handler to cancel every second block we attempt to break:

BlockBreakListener.java
7 collapsed lines
package com.learnpaperdev.beginner;
import net.kyori.adventure.text.logger.slf4j.ComponentLogger;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent;
import org.jspecify.annotations.NullMarked;
@NullMarked
public final class BlockBreakListener implements Listener {
private final ComponentLogger logger;
private int counter = 0;
public BlockBreakListener(ComponentLogger logger) {
this.logger = logger;
}
@EventHandler
void onBlockBreak(BlockBreakEvent event) {
counter++;
// Every second block break
if (counter % 2 == 0) {
event.setCancelled(true);
}
logger.warn("Was block breaking of {} cancelled: {}",
event.getBlock().getType(), event.isCancelled()
);
}
}

Since our event listener is an actual object, we can store variables, just like with any other class. If you restart the server now, you should be able to see the effect of this change.

And the console should also display the status:

Terminal window
[11:53:28 WARN]: [BeginnerPlugin] Was block breaking of SHORT_GRASS cancelled: false
[11:53:29 WARN]: [BeginnerPlugin] Was block breaking of GRASS_BLOCK cancelled: true
[11:53:30 WARN]: [BeginnerPlugin] Was block breaking of GRASS_BLOCK cancelled: false
[11:53:31 WARN]: [BeginnerPlugin] Was block breaking of GRASS_BLOCK cancelled: true

With this being the first content focused page, I can now also give you a task that you can optionally do if you want to exercise on the aspects we have discussed here.

The first task is: You can create and register a new event listener which completely cancels all damage to any entity? (Tip: EntityDamageEvent). The solution can be found here.

Learn Paper Dev is licensed under CC BY-NC-SA 4.0