Paper Plugin Guide
File mode0

Paper essentials

Entities and mobs

Spawn, customize, find and control mobs and other entities.

Intermediate50 min read

Zombies, cows, arrows, dropped items and even players are all entities. On this page you will spawn mobs, change how they look and fight, find them near a player, react when they take damage or die, throw projectiles, and finish with a /boss command that summons a Zombie King with a boss bar and special loot.

What is an entity?

An entity is anything in the world that is not a block and can move or exist on its own: a zombie, a cow, an arrow in flight, a dropped diamond on the ground, a minecart, an armor stand, and every player. Blocks stay in a fixed grid and entities do not. An entity has a position, a direction it faces, a speed, and usually a name and some other properties.

Paper organizes entity types in a family tree. Each type is a more specific kind of the one above it, and it inherits everything its parent can do. That matters to you because a method you learn once, like setCustomName, works on a zombie, a cow and a player alike.

Entity types in Paper Entity is the top type. LivingEntity extends Entity. Mob extends LivingEntity, then Creature, Monster and Zombie. HumanEntity extends LivingEntity and Player extends HumanEntity. Arrows mean is a kind of. Entity has a location LivingEntity has health Mob computer-controlled Creature an in-between type Monster hostile mobs Zombie a zombie HumanEntity has an inventory Player a real person How to read this Each arrow means "is a kind of". A Zombie is a Monster, a Creature, a Mob, a LivingEntity and an Entity. So every method of those types also works on a Zombie.
A Zombie is a Monster, a Creature, a Mob, a LivingEntity and an Entity, so every method of those types also works on a Zombie.

These are the types you will meet most often:

TypeWhat it addsExamples
EntityA location, a velocity, a name, remove(), teleport()Everything below
LivingEntityHealth, potion effects, attributes, equipment, damageZombies, cows, players, armor stands
MobA "brain" (AI) and a target to chase or followZombies, cows, villagers
MonsterMarks hostile mobsZombies, skeletons, creepers
AnimalsMarks passive farm and wild animalsCows, sheep, wolves
PlayerA real person: inventory, game mode, chatYou
ItemA dropped item lying on the groundA dropped diamond
ProjectileSomething thrown or shot, with a shooterArrows, snowballs, fireballs

The family tree is Java inheritance, and Paper uses it constantly. A plain Java program shows the idea. Run it, then read the notes:

MobFamily.javaRuns on Java 25
1interface Entity {2    String describe();3}4 5interface LivingEntity extends Entity {6    double health();7}8 9interface Monster extends LivingEntity {10}11 12record Zombie(double health) implements Monster {13    public String describe() {14        return "a zombie";15    }16}17 18record Cow(double health) implements LivingEntity {19    public String describe() {20        return "a cow";21    }22}23 24record DroppedItem(String material) implements Entity {25    public String describe() {26        return "a dropped " + material;27    }28}29 30void inspect(Entity entity) {31    IO.println("Found " + entity.describe());32    if (entity instanceof LivingEntity living) {33        IO.println("  It has health: " + living.health());34    }35    if (entity instanceof Monster) {36        IO.println("  It is hostile!");37    }38}39 40void main() {41    inspect(new Zombie(20.0));42    inspect(new Cow(10.0));43    inspect(new DroppedItem("diamond"));44}
  1. A living entity is a kind of entity, so it promises everything an Entity promises, plus its own health().
  2. A zombie is a Monster, which is a LivingEntity, which is an Entity. That one line gives it all three sets of abilities.
  3. This method accepts any entity at all: a zombie, a cow or a dropped item.
  4. Asks "is this entity also a living entity?" If yes, Java gives you the same object under the more specific name living, so you can call health() on it. This pattern matching trick is how nearly every Paper plugin checks entity types.

In a plugin it looks the same. An event gives you a plain Entity, and you ask entity instanceof Zombie zombie to find out whether it is the kind you care about. If you want every kind of the entity type as a value, entity.getType() returns an EntityType such as EntityType.ZOMBIE. That is an enum: a fixed list of names.

Spawning a mob

To create an entity, ask the World to spawn one at a Location. You say which kind with the class name followed by .class:

The shortest spawn
1Zombie zombie = world.spawn(location, Zombie.class);

The answer is the new zombie, typed as a Zombie, so you can change it right away. That is better than the older world.spawnEntity(location, EntityType.ZOMBIE), which returns a plain Entity that you would have to convert yourself.

There is a third form that is even better for customizing, because it lets you set up the mob before it appears in the world. You hand spawn a small piece of code, a lambda, that runs on the new mob first. Nobody ever sees a zombie with the wrong name for a split second. Here is a command that summons a glowing baby zombie that hunts you:

HunterCommand.javaCompiles on Paper 26.3Compile Lab
Where this file livesentities-mobssrcmainjavacomexampleentitiesmobsHunterCommand.java

The package com.example.entitiesmobs is the folder path com/example/entitiesmobs 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.

  • entities-mobs/
    • src/main/
      • java/com/example/entitiesmobs/Package com.example.entitiesmobs
        • EntitiesMobsPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • HunterCommand.javayou are hereCommand (BasicCommand)
        • TankCommand.javaCommand (BasicCommand)
      • 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.entitiesmobs;2 3import io.papermc.paper.command.brigadier.BasicCommand;4import io.papermc.paper.command.brigadier.CommandSourceStack;5import net.kyori.adventure.text.Component;6import net.kyori.adventure.text.format.NamedTextColor;7import org.bukkit.Location;8import org.bukkit.entity.Player;9import org.bukkit.entity.Zombie;10 11public final class HunterCommand implements BasicCommand {12 13    @Override14    public void execute(CommandSourceStack source, String[] args) {15        if (!(source.getExecutor() instanceof Player player)) {16            source.getSender().sendRichMessage("<red>Only players can summon a hunter.");17            return;18        }19        Location inFront = player.getLocation().add(player.getLocation().getDirection().multiply(3));20        Zombie hunter = player.getWorld().spawn(inFront, Zombie.class, zombie -> {21            zombie.customName(Component.text("Hunter", NamedTextColor.RED));22            zombie.setCustomNameVisible(true);23            zombie.setGlowing(true);24            zombie.setBaby();25            zombie.setShouldBurnInDay(false);26        });27        hunter.setTarget(player);28        player.sendRichMessage("<red>A hunter appeared three blocks in front of you!");29    }30}
  1. A simple command, exactly like the ones on Commands, part 1.
  2. Only a player has a position to spawn at. The console gets a friendly refusal.
  3. Starts at the player's feet and moves three blocks in the direction the player is looking. getLocation() gives you a copy, so adding to it does not move the player.
  4. Spawns a zombie at that location. The third argument is the lambda.
  5. The lambda. Paper calls it with the brand-new zombie, named zombie here, before the zombie enters the world. Everything between the braces customizes it.
  6. Gives the zombie a name. It takes a Component (formatted text), so you can color it. See Text and colors with Adventure.
  7. Without this, the name only appears when you point at the mob. With it, the name floats above the zombie's head all the time.
  8. Draws the colored outline you know from the glowing effect, even through walls. Great for finding a mob you just spawned.
  9. Makes the zombie a small, fast baby zombie. Older tutorials write setBaby(true), which is deprecated; setBaby() is the modern call.
  10. Stops the sun from setting the zombie on fire, so it survives until morning.
  11. After the zombie exists, tells its brain "chase this player". Mobs normally pick their own target, but you can choose for them.

Not every kind of entity is as easy to spawn as a zombie. Spawning a Player is not possible, because a player is a real person who connects, and spawning an Item works better with world.dropItem(location, itemStack). Entities that need special data, such as paintings, will refuse when something is missing. If the class cannot be spawned, spawn throws an IllegalArgumentException.

Customizing a mob

The whole point of a plugin is to make mobs that the vanilla game never produces. These are the knobs you have, grouped by what they change.

Name, glow and sound

CallEffect
customName(Component)Sets the name. Use customName(null) to remove it.
setCustomNameVisible(true)Shows the name all the time instead of only when aimed at.
setGlowing(true)Outline visible through walls.
setSilent(true)The mob makes no sounds.
setInvulnerable(true)Takes no damage from most sources.
setGravity(false)Floats in the air. Handy for decorations.

Older plugins use setCustomName(String) with &c color codes. That method still exists but is deprecated, because text with colors belongs in a Component.

Attributes: health, speed and damage

An attribute is a number that describes how strong a living thing is. Every LivingEntity has a set of them. You read one with getAttribute(Attribute.X), which returns an AttributeInstance, and you change its base value with setBaseValue.

AttributeControlsA normal zombie has
Attribute.MAX_HEALTHHow many half-hearts the mob can have20 (ten hearts)
Attribute.MOVEMENT_SPEEDHow fast it walksabout 0.23
Attribute.ATTACK_DAMAGEHow hard it hits, in half-hearts3
Attribute.KNOCKBACK_RESISTANCEHow hard it is to push back (0 to 1)0

The next example uses all of this together. Read how the attribute work moved into a small helper method, because getAttribute can return null for an entity type that does not have that attribute:

TankCommand.javaCompiles on Paper 26.3Compile Lab
Where this file livesentities-mobssrcmainjavacomexampleentitiesmobsTankCommand.java

The package com.example.entitiesmobs is the folder path com/example/entitiesmobs 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.

  • entities-mobs/
    • src/main/
      • java/com/example/entitiesmobs/Package com.example.entitiesmobs
        • EntitiesMobsPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • HunterCommand.javaCommand (BasicCommand)
        • TankCommand.javayou are hereCommand (BasicCommand)
      • 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.entitiesmobs;2 3import io.papermc.paper.command.brigadier.BasicCommand;4import io.papermc.paper.command.brigadier.CommandSourceStack;5import net.kyori.adventure.text.Component;6import net.kyori.adventure.text.format.NamedTextColor;7import org.bukkit.Location;8import org.bukkit.Material;9import org.bukkit.attribute.Attribute;10import org.bukkit.attribute.AttributeInstance;11import org.bukkit.entity.Player;12import org.bukkit.entity.Zombie;13import org.bukkit.inventory.EntityEquipment;14import org.bukkit.inventory.ItemStack;15 16public final class TankCommand implements BasicCommand {17 18    @Override19    public void execute(CommandSourceStack source, String[] args) {20        if (!(source.getExecutor() instanceof Player player)) {21            source.getSender().sendRichMessage("<red>Only players can summon a tank.");22            return;23        }24        Location inFront = player.getLocation().add(player.getLocation().getDirection().multiply(4));25        player.getWorld().spawn(inFront, Zombie.class, zombie -> {26            zombie.customName(Component.text("Tank", NamedTextColor.GRAY));27            zombie.setCustomNameVisible(true);28            setBase(zombie.getAttribute(Attribute.MAX_HEALTH), 60.0);29            zombie.setHealth(60.0);30            setBase(zombie.getAttribute(Attribute.MOVEMENT_SPEED), 0.18);31            setBase(zombie.getAttribute(Attribute.ATTACK_DAMAGE), 6.0);32            dress(zombie.getEquipment());33            zombie.setRemoveWhenFarAway(true);34        });35        player.sendRichMessage("<gray>A tank lumbers toward you. It has 60 health.");36    }37 38    private void setBase(AttributeInstance attribute, double value) {39        if (attribute != null) {40            attribute.setBaseValue(value);41        }42    }43 44    private void dress(EntityEquipment equipment) {45        if (equipment == null) {46            return;47        }48        equipment.setHelmet(ItemStack.of(Material.IRON_HELMET));49        equipment.setChestplate(ItemStack.of(Material.IRON_CHESTPLATE));50        equipment.setItemInMainHand(ItemStack.of(Material.IRON_SWORD));51        equipment.setHelmetDropChance(0.0f);52        equipment.setChestplateDropChance(0.0f);53        equipment.setItemInMainHandDropChance(1.0f);54    }55}
  1. Triples the zombie's health ceiling. The helper method below does the actual change.
  2. Fills the new, bigger health bar. Without this line the tank would start with 20 health.
  3. A little slower than a normal zombie (0.23), which suits something heavy.
  4. Hits for six half-hearts instead of three.
  5. Gives the zombie armor and a weapon in a separate method, so this lambda stays easy to read.
  6. Lets the tank vanish when every player walks away, like any normal zombie. Zombies already do this by default, so the line only states the intent. The boss at the end of this page uses false to do the opposite.
  7. A helper method. It changes the attribute only when there is one, so a missing attribute cannot crash the command. See Methods for why helpers like this keep code tidy.
  8. Some entities have no equipment. A zombie does, but checking costs one line and prevents a crash.
  9. Puts an iron helmet on the mob's head. ItemStack.of(Material.IRON_HELMET) creates the item; see Items and ItemStacks.
  10. 0.0 means "never drop this when the mob dies". The helmet is just decoration.
  11. 1.0 means "always drop this when a player kills the mob". Killing the tank always hands you its iron sword.

Equipment and drop chances

Mobs wear and hold items through getEquipment(). It has a pair of methods for each slot: setHelmet, setChestplate, setLeggings, setBoots, setItemInMainHand and setItemInOffHand. Each slot also has a drop chance:

  • 0.0f never drops.
  • 1.0f always drops when a player killed the mob.
  • Above 1.0f always drops, even if lava or a fall killed the mob.

Vanilla mobs only rarely drop what they wear. If you hand a mob a rare item and forget the drop chance, players will almost never get it back.

Switching the brain off

CallWhat the mob does
setAware(false)Stands still and does nothing on its own, but still moves if it is pushed or hit. Best for statues and shop NPCs.
setAI(false)Is completely frozen: the whole brain is off and it cannot move at all.
setTarget(player)Keeps its AI but now chases this specific living entity.

Persistence: does the mob survive?

Two similar-sounding settings answer two different questions, and beginners mix them up all the time:

  • setRemoveWhenFarAway(boolean): despawning. When true, the mob disappears if no player is near, like a random zombie wandering on the surface. When false, it stays loaded and counts as a permanent resident.
  • setPersistent(boolean): saving. When true (the default), the mob is written to the world file and is still there after a restart. When false, the mob disappears when the chunk unloads or the server stops.

Finding entities

You often need to answer "what is around here?". Paper offers several finders, and which one you use depends on how much you want to search:

CallReturnsUse it when
entity.getNearbyEntities(x, y, z)Every entity in a box around that entityYou already have an entity in hand
world.getNearbyEntities(location, x, y, z)Every entity in a box around a spotYou have a location, not an entity
world.getNearbyLivingEntities(location, radius)Only living thingsYou do not care about dropped items and arrows
world.getNearbyPlayers(location, radius)Only playersA message or bar for everyone close
world.getNearbyEntitiesByType(Monster.class, location, radius)Only that class, already typedYou want just zombies, just monsters, and so on
world.getEntitiesByClass(Cow.class)Every cow in the whole worldA rare, whole-world check
You A B C getNearbyEntities(here, 15, 15, 15) A is found Inside the box and the circle. B is found too In a corner: farther than 15 blocks. C is not found Outside the box.
The search area is a box, not a circle. Entities in the corners are farther away than the number you passed.

The number you give is half the box size, measured from the center to each side. A radius of 15 searches a cube 30 blocks wide. If you need an exact distance, ask each result with entity.getLocation().distance(here) and skip the ones that are too far.

This command lists the living things within 15 blocks of the player:

NearbyCommand.javaCompiles on Paper 26.3Compile Lab
Where this file livesentities-findsrcmainjavacomexampleentitiesfindNearbyCommand.java

The package com.example.entitiesfind is the folder path com/example/entitiesfind 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.

  • entities-find/
    • src/main/
      • java/com/example/entitiesfind/Package com.example.entitiesfind
        • ClearMobsCommand.javaCommand (BasicCommand)
        • EntitiesFindPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • NearbyCommand.javayou are hereCommand (BasicCommand)
      • 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.entitiesfind;2 3import io.papermc.paper.command.brigadier.BasicCommand;4import io.papermc.paper.command.brigadier.CommandSourceStack;5import java.util.Collection;6import org.bukkit.Location;7import org.bukkit.entity.LivingEntity;8import org.bukkit.entity.Player;9 10public final class NearbyCommand implements BasicCommand {11 12    private static final double RADIUS = 15.0;13 14    @Override15    public void execute(CommandSourceStack source, String[] args) {16        if (!(source.getExecutor() instanceof Player player)) {17            source.getSender().sendRichMessage("<red>Only players can look around.");18            return;19        }20        Location here = player.getLocation();21        Collection<LivingEntity> nearby = player.getWorld().getNearbyLivingEntities(here, RADIUS);22        int shown = 0;23        for (LivingEntity creature : nearby) {24            if (creature.equals(player)) {25                continue;26            }27            double distance = creature.getLocation().distance(here);28            player.sendRichMessage("<gray> - <white>" + creature.getType().name()29                + " <gray>" + String.format("%.1f", distance) + " blocks away");30            shown++;31        }32        if (shown == 0) {33            player.sendRichMessage("<yellow>Nothing living within " + (int) RADIUS + " blocks.");34        }35    }36}
  1. One named number for the search size, so you change it in one place.
  2. The finder returns a Collection, which is Java's word for "a group of things you can loop over". The part in angle brackets says what is inside.
  3. Finds every living entity in the box around the player. Dropped items and arrows are skipped.
  4. Runs the loop body once for each creature found. See Loops.
  5. The finder includes the player at the center. This check skips them, so the list only shows others.
  6. The straight-line distance in blocks, which is what the player expects to read.
  7. The entity type as text, such as ZOMBIE.
  8. Rounds the distance to one decimal place so it reads "4.2" and not "4.19999837".

Here is what the player sees after standing between a zombie and two cows:

- ZOMBIE 4.2 blocks away
- COW 8.9 blocks away
- COW 12.4 blocks away

Damage and death events

Two events let you react to fights:

  • EntityDamageEvent fires right before any entity takes damage, from a sword, a fall, fire, an explosion or anything else. You can read getCause() (the kind of damage) and getFinalDamage() (how much will really be taken after armor), or cancel it.
  • EntityDamageByEntityEvent is a more specific version that fires when something hit it. It adds getDamager(). You can listen to either one.
  • EntityDeathEvent fires when a living entity dies. It tells you what will drop (getDrops()), how much experience (getDroppedExp()), and who did it (the dead entity's getKiller()).

Both events are used by the boss example at the end of this page. The key trick is the first line of each handler. A single event handler hears every entity in the world, so the handler's first job is to throw away everything that is not the boss:

BossListener.javaCompiles on Paper 26.3Compile Lab
Where this file livesentities-demosrcmainjavacomexampleentitiesdemoBossListener.java

The package com.example.entitiesdemo is the folder path com/example/entitiesdemo 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.

  • entities-demo/
    • src/main/
      • java/com/example/entitiesdemo/Package com.example.entitiesdemo
        • BossCommand.javaCommand (BasicCommand)
        • BossListener.javayou are hereListener: reacts to events
        • BossManager.javaHelper class
        • EntitiesDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
      • 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.

42@EventHandler43public void onDeath(EntityDeathEvent event) {44    if (!(event.getEntity() instanceof Zombie boss) || !bosses.isBoss(boss)) {45        return;46    }47    bosses.finish(boss);48    event.getDrops().clear();49    event.getDrops().add(ItemStack.of(Material.DIAMOND, 8));50    event.getDrops().add(createTrophy());51    event.setDroppedExp(500);52    Player killer = boss.getKiller();53    if (killer == null) {54        Bukkit.broadcast(MINI_MESSAGE.deserialize("<gray>The Zombie King has fallen."));55    } else {56        Bukkit.broadcast(MINI_MESSAGE.deserialize(57            "<gold><killer></gold> <gray>defeated the Zombie King!",58            Placeholder.unparsed("killer", killer.getName())));59    }60}
  1. Two checks in one line. Is it a zombie, and is it one of the zombies we registered as a boss? If either answer is no, the handler returns at once and ordinary mobs never reach the rest.
  2. The drops list is a normal editable list. Clearing it removes the usual rotten flesh and the equipment.
  3. Adds eight diamonds to what the zombie drops.
  4. Experience orbs worth 500 points fall, enough to level up a lot.
  5. The player who landed the final blow. It is null when something else killed the boss, so the next lines handle both cases.

Projectiles

A projectile is an entity that flies on its own after being thrown or shot: a snowball, an arrow, an egg, a fireball, an ender pearl. Every living entity can launch one with launchProjectile, which creates the projectile at the shooter and sends it flying.

ClassWhat it is
Snowball, EggLight throwables that push back what they hit
Arrow, SpectralArrow, TridentShot or thrown weapons
Fireball, SmallFireball, WitherSkullExplosive or burning shots
EnderPearl, ThrownPotion, ThrownExpBottle, WindChargeThrowables with special effects
SnowBlastCommand.javaCompiles on Paper 26.3Compile Lab
Where this file livesentities-projectilessrcmainjavacomexampleentitiesprojectilesSnowBlastCommand.java

The package com.example.entitiesprojectiles is the folder path com/example/entitiesprojectiles 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.

  • entities-projectiles/
    • src/main/
      • java/com/example/entitiesprojectiles/Package com.example.entitiesprojectiles
        • EntitiesProjectilesPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • SnowBlastCommand.javayou are hereCommand (BasicCommand)
        • SnowBlastListener.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.

13@Override14public void execute(CommandSourceStack source, String[] args) {15    if (!(source.getExecutor() instanceof Player player)) {16        source.getSender().sendRichMessage("<red>Only players can throw snowballs.");17        return;18    }19    Vector fast = player.getLocation().getDirection().multiply(2.5);20    Snowball snowball = player.launchProjectile(Snowball.class, fast);21    snowball.addScoreboardTag(BLAST_TAG);22}
  1. A Vector is an arrow with a direction and a length. getDirection() is the way the player faces, with length 1. Multiplying by 2.5 makes the snowball fly two and a half times faster.
  2. Creates a snowball that starts at the player and flies along the vector. The result is the snowball, so you can keep changing it.
  3. Sticks a text label on this one snowball. Every entity can carry tags, and it is the easiest way to mark "this is one of mine" so a listener can tell your snowballs from ordinary ones.

The projectile flies on its own until it hits something. At that moment Paper fires ProjectileHitEvent. The event tells you what the projectile was (getEntity()), what it hit if it was a creature (getHitEntity()), and what it hit if it was a block (getHitBlock()). When one of the last two has a value, the other is null.

SnowBlastListener.javaCompiles on Paper 26.3Compile Lab
Where this file livesentities-projectilessrcmainjavacomexampleentitiesprojectilesSnowBlastListener.java

The package com.example.entitiesprojectiles is the folder path com/example/entitiesprojectiles 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.

  • entities-projectiles/
    • src/main/
      • java/com/example/entitiesprojectiles/Package com.example.entitiesprojectiles
        • EntitiesProjectilesPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • SnowBlastCommand.javaCommand (BasicCommand)
        • SnowBlastListener.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.

12@EventHandler13public void onProjectileHit(ProjectileHitEvent event) {14    if (!(event.getEntity() instanceof Snowball snowball)) {15        return;16    }17    if (!snowball.getScoreboardTags().contains(SnowBlastCommand.BLAST_TAG)) {18        return;19    }20    if (!(snowball.getShooter() instanceof Player shooter)) {21        return;22    }23    if (event.getHitEntity() instanceof LivingEntity victim) {24        victim.damage(6.0, shooter);25        shooter.sendRichMessage("<aqua>Direct hit!");26    } else if (event.getHitBlock() != null) {27        shooter.sendRichMessage("<gray>Splat. Your snowball hit "28            + event.getHitBlock().getType().name() + ".");29    }30}
  1. The event can be about any projectile, an arrow or a fireball. This keeps only snowballs and names the result snowball.
  2. Ignores ordinary snowballs. Only the ones tagged by /snowblast pass.
  3. Who threw it. A dispenser or a snow golem can shoot snowballs too, so the code makes sure it was a player.
  4. Checks that the thing it hit is a creature, not an item frame or a boat.
  5. Deals six half-hearts of damage and credits shooter with it. That credit means kill messages and the dead mob's getKiller() name the right player.
  6. If no creature was hit, the snowball hit a block instead.
Direct hit!
Splat. Your snowball hit STONE.

Removing entities

There are three ways to get rid of a mob, and they behave differently:

CallWhat happensDeath event?Drops?
entity.remove()The entity vanishes instantly, with no animation.NoNo
livingEntity.setHealth(0)The mob dies as if it were killed.YesYes, the normal ones
livingEntity.damage(1000, player)The mob takes a huge hit and dies, credited to player.YesYes, and the player gets the kill

Use remove() for things that should simply go away: cleaning up a custom NPC, deleting a decoration, erasing a spawned mob when your minigame ends. Use setHealth(0) or damage when the world should react as if a death happened, for example for loot and score keeping.

You cannot remove a Player; Paper throws an UnsupportedOperationException if you try. To get a player off the server, use player.kick(...).

ClearMobsCommand.javaCompiles on Paper 26.3Compile Lab
Where this file livesentities-findsrcmainjavacomexampleentitiesfindClearMobsCommand.java

The package com.example.entitiesfind is the folder path com/example/entitiesfind 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.

  • entities-find/
    • src/main/
      • java/com/example/entitiesfind/Package com.example.entitiesfind
        • ClearMobsCommand.javayou are hereCommand (BasicCommand)
        • EntitiesFindPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • NearbyCommand.javaCommand (BasicCommand)
      • 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.

13@Override14public void execute(CommandSourceStack source, String[] args) {15    if (!(source.getExecutor() instanceof Player player)) {16        source.getSender().sendRichMessage("<red>Only players can clear mobs.");17        return;18    }19    Collection<Monster> monsters = player.getWorld()20        .getNearbyEntitiesByType(Monster.class, player.getLocation(), RADIUS);21    for (Monster monster : monsters) {22        monster.remove();23    }24    player.sendRichMessage("<green>Removed " + monsters.size() + " monsters.");25}
  1. Asks for only monsters near the player, so cows, villagers and dropped items are safe. The result is already typed as Collection<Monster>.
  2. Removes each monster instantly, without drops or a death event. That is what you want for a clean-up command.
  3. A collection knows how many things it holds, so the message can say how many were removed.

Example plugin: the Zombie King

Now we put it all together. /boss spawns a Zombie King with 200 health, strong attacks, diamond armor, a boss bar for everyone nearby, and special loot. The plugin has four small files, and each has one job:

  • entities-demo/
    • src/
      • main/
        • java/
          • com/example/entitiesdemo/
            • EntitiesDemoPlugin.javaCreates the boss manager and registers the command and the listener
            • BossCommand.javaThe /boss command: asks the manager to spawn a boss
            • BossManager.javaSpawns the boss, and keeps one boss bar per boss
            • BossListener.javaUpdates the bar when the boss is hurt and gives the loot when it dies
        • resources/
          • plugin.ymlPlugin description and the entitiesdemo.boss permission

The command and the listener must share the same list of bosses, so the main class creates one BossManager and hands it to both. That sharing is called passing a dependency, and you will meet it again on Organizing a bigger plugin.

EntitiesDemoPlugin.javaCompiles on Paper 26.3Compile Lab
Where this file livesentities-demosrcmainjavacomexampleentitiesdemoEntitiesDemoPlugin.java

The package com.example.entitiesdemo is the folder path com/example/entitiesdemo 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.

  • entities-demo/
    • src/main/
      • java/com/example/entitiesdemo/Package com.example.entitiesdemo
        • BossCommand.javaCommand (BasicCommand)
        • BossListener.javaListener: reacts to events
        • BossManager.javaHelper class
        • EntitiesDemoPlugin.javayou are hereMain class: Paper starts here (named as main in plugin.yml)
      • 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.entitiesdemo;2 3import io.papermc.paper.command.brigadier.Commands;4import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents;5import org.bukkit.plugin.java.JavaPlugin;6 7public final class EntitiesDemoPlugin extends JavaPlugin {8 9    @Override10    public void onEnable() {11        BossManager bosses = new BossManager();12        getServer().getPluginManager().registerEvents(new BossListener(bosses), this);13        getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, event -> {14            Commands commands = event.registrar();15            commands.register("boss", "Summon the Zombie King", new BossCommand(bosses));16        });17    }18}
  1. The one and only manager. Both the listener and the command receive this same object.
  2. The listener gets the manager through its constructor, the special method that builds the object.
  3. The command gets the same manager, so a boss spawned by the command is recognized by the listener.

The heart of the plugin is the manager's spawnBoss method. It uses almost everything from this page: the spawn lambda, a name, glow, attributes, equipment and persistence.

BossManager.javaCompiles on Paper 26.3Compile Lab
Where this file livesentities-demosrcmainjavacomexampleentitiesdemoBossManager.java

The package com.example.entitiesdemo is the folder path com/example/entitiesdemo 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.

  • entities-demo/
    • src/main/
      • java/com/example/entitiesdemo/Package com.example.entitiesdemo
        • BossCommand.javaCommand (BasicCommand)
        • BossListener.javaListener: reacts to events
        • BossManager.javayou are hereHelper class
        • EntitiesDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
      • 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.

29public void spawnBoss(Player summoner) {30    World world = summoner.getWorld();31    Location spot = summoner.getLocation().add(summoner.getLocation().getDirection().multiply(5));32    Zombie boss = world.spawn(spot, Zombie.class, zombie -> {33        zombie.customName(BOSS_NAME);34        zombie.setCustomNameVisible(true);35        zombie.setGlowing(true);36        zombie.setShouldBurnInDay(false);37        zombie.setPersistent(false);38        zombie.setRemoveWhenFarAway(false);39        setBase(zombie.getAttribute(Attribute.MAX_HEALTH), BOSS_HEALTH);40        setBase(zombie.getAttribute(Attribute.ATTACK_DAMAGE), 8.0);41        setBase(zombie.getAttribute(Attribute.MOVEMENT_SPEED), 0.27);42        setBase(zombie.getAttribute(Attribute.KNOCKBACK_RESISTANCE), 0.8);43        zombie.setHealth(BOSS_HEALTH);44        equip(zombie.getEquipment());45    });46    BossBar bar = BossBar.bossBar(BOSS_NAME, 1.0f, BossBar.Color.RED, BossBar.Overlay.NOTCHED_10);47    bars.put(boss.getUniqueId(), bar);48    for (Player nearby : world.getNearbyPlayers(spot, BAR_RANGE)) {49        nearby.showBossBar(bar);50    }51}
  1. Five blocks in front of the player, so the boss does not appear on top of them.
  2. If the server restarts, the boss is not saved. The boss bars live only in memory, so a saved boss would come back with no bar and no plugin record.
  3. The boss must not despawn just because the player ran far away.
  4. Raises the maximum to 200 half-hearts, which is a hundred hearts.
  5. Players can hit the boss without it flying far across the room.
  6. Starts the fight at full health.
  7. Creates the boss bar at the top of the screen: the name, a full bar (1.0), red color and a style with ten notches.
  8. Remembers "this boss owns this bar" in a map. A UUID is a unique id every entity has, and it is the safe way to recognize an entity again later.
  9. Finds every player within 40 blocks, because only they should see the bar.
  10. Makes the bar appear on that player's screen.

The other manager methods are short. isBoss asks the map whether an entity is registered. updateBar works out the new fill: the health the boss will have after the hit, divided by the maximum. finish removes the bar when the boss is dead.

BossManager.javaCompiles on Paper 26.3Compile Lab
Where this file livesentities-demosrcmainjavacomexampleentitiesdemoBossManager.java

The package com.example.entitiesdemo is the folder path com/example/entitiesdemo 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.

  • entities-demo/
    • src/main/
      • java/com/example/entitiesdemo/Package com.example.entitiesdemo
        • BossCommand.javaCommand (BasicCommand)
        • BossListener.javaListener: reacts to events
        • BossManager.javayou are hereHelper class
        • EntitiesDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
      • 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.

64public void updateBar(Zombie boss, double incomingDamage) {65    BossBar bar = bars.get(boss.getUniqueId());66    if (bar == null) {67        return;68    }69    double healthLeft = Math.max(0.0, boss.getHealth() - incomingDamage);70    bar.progress((float) (healthLeft / BOSS_HEALTH));71}
  1. The damage event fires before the damage is applied, so the boss still shows its old health. Subtracting the incoming damage gives the health it is about to have.
  2. Never lets the number go below zero, in case a huge hit overshoots.
  3. A boss bar fill is a number from 0 to 1, as a float. The cast (float) converts the larger double.
BossListener.javaCompiles on Paper 26.3Compile Lab
Where this file livesentities-demosrcmainjavacomexampleentitiesdemoBossListener.java

The package com.example.entitiesdemo is the folder path com/example/entitiesdemo 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.

  • entities-demo/
    • src/main/
      • java/com/example/entitiesdemo/Package com.example.entitiesdemo
        • BossCommand.javaCommand (BasicCommand)
        • BossListener.javayou are hereListener: reacts to events
        • BossManager.javaHelper class
        • EntitiesDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
      • 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.

30@EventHandler(ignoreCancelled = true)31public void onDamage(EntityDamageEvent event) {32    if (!(event.getEntity() instanceof Zombie boss) || !bosses.isBoss(boss)) {33        return;34    }35    bosses.updateBar(boss, event.getFinalDamage());36    if (event instanceof EntityDamageByEntityEvent byEntity37        && byEntity.getDamager() instanceof Player attacker) {38        bosses.showBarTo(boss, attacker);39    }40}
  1. The option ignoreCancelled = true skips hits that another plugin already canceled, so the bar only drops when the boss really takes damage. See Event priorities and canceling.
  2. Moves the bar to match the new health. getFinalDamage() is the amount after armor and resistance.
  3. If the damage came from another entity, this unlocks getDamager().
  4. Players who join the fight late, from farther than 40 blocks, get the bar the moment they hit the boss.

Finally the trophy. createTrophy builds a nether star with a gold name. The name uses decoration(TextDecoration.ITALIC, false) because item names are italic by default in Minecraft. Items and ItemStacks explains why.

BossListener.javaCompiles on Paper 26.3Compile Lab
Where this file livesentities-demosrcmainjavacomexampleentitiesdemoBossListener.java

The package com.example.entitiesdemo is the folder path com/example/entitiesdemo 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.

  • entities-demo/
    • src/main/
      • java/com/example/entitiesdemo/Package com.example.entitiesdemo
        • BossCommand.javaCommand (BasicCommand)
        • BossListener.javayou are hereListener: reacts to events
        • BossManager.javaHelper class
        • EntitiesDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
      • 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.

62private ItemStack createTrophy() {63    ItemStack trophy = ItemStack.of(Material.NETHER_STAR);64    Component name = Component.text("Zombie King's Trophy", NamedTextColor.GOLD)65        .decoration(TextDecoration.ITALIC, false);66    trophy.setData(DataComponentTypes.CUSTOM_NAME, name);67    return trophy;68}
  1. The item itself, one nether star.
  2. Turns the default italic style off so the name looks like a real rare item.
  3. The modern way to name an item: a data component. See Items and ItemStacks.

Here is what the fight looks like. The boss bar sits at the top of the screen and shrinks as you hit the boss:

Zombie King
Zombie King

When the boss falls, everyone sees the announcement and the loot drops:

Steve defeated the Zombie King!
Zombie King's Trophy

You can download the whole plugin, build it, join your test server (see Build, install and test), and type /boss. You need the permission entitiesdemo.boss, which operators have by default (use /op on your own test server).

Mistakes to avoid

  • Forgetting setHealth after MAX_HEALTH. The mob keeps its old, smaller health.
  • Handling every entity. EntityDamageEvent and EntityDeathEvent fire for all creatures in the world. Check the type and your own marker on the first lines, and return early.
  • Assuming getKiller(), getHitEntity() or getAttribute() always have a value. They can be null. Check before you use them.
  • Using remove() when you wanted a kill. Nothing drops and no death event fires, so your loot and score code never runs.
  • Searching the whole world. Use a small box near the action, and not many times per second.
  • Storing the entity object forever. The chunk can unload and the entity can die. Store its UUID and look it up again, or check isValid().
  • Setting entity properties from another thread. Like most of the API, entities may only be changed on the main thread. See Threads, performance and lag.
Try it

Lucky zombies

Make zombies drop a golden apple 20 percent of the time, but only when a player kills them. Skeletons, cows and zombies killed by lava must not be affected.

Hint 1

Listen to EntityDeathEvent. Check event.getEntity() instanceof Zombie first, then check that getKiller() is not null.

Hint 2

For a chance, ask Java for a random number between 0 and 1 with ThreadLocalRandom.current().nextDouble() and compare it to 0.2. Add the apple with event.getDrops().add(...).

Show the solution

The two guard lines throw away everything that is not a zombie killed by a player. The random number is below 0.2 about one time in five, which gives the 20 percent chance.

LuckyLootListener.javaCompiles on Paper 26.3Compile Lab
Where this file livesentities-exercisesrcmainjavacomexampleentitiesexerciseLuckyLootListener.java

The package com.example.entitiesexercise is the folder path com/example/entitiesexercise 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.

  • entities-exercise/
    • src/main/
      • java/com/example/entitiesexercise/Package com.example.entitiesexercise
        • EntitiesExercisePlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • LuckyLootListener.javayou are hereListener: reacts to events
        • PetCommand.javaCommand (BasicCommand)
      • 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.

15@EventHandler16public void onDeath(EntityDeathEvent event) {17    if (!(event.getEntity() instanceof Zombie zombie)) {18        return;19    }20    if (zombie.getKiller() == null) {21        return;22    }23    if (ThreadLocalRandom.current().nextDouble() < APPLE_CHANCE) {24        event.getDrops().add(ItemStack.of(Material.GOLDEN_APPLE));25    }26}
  1. No player involved, so no bonus loot.
  2. A random decimal from 0 up to (but not including) 1. Below 0.2 happens one time in five.
Try it

A loyal wolf

Write a /pet command that spawns a tamed wolf at the player's feet, owned by the player, with a red collar and the name "Steve's pet" (using the player's real name).

Hint

A Wolf is Tameable. Look at the methods setTamed and setOwner in autocomplete, then look for a collar method.

Show the solution

Spawn the wolf with the lambda form and do everything inside it, so the wolf is already tame the moment it appears:

PetCommand.javaCompiles on Paper 26.3Compile Lab
Where this file livesentities-exercisesrcmainjavacomexampleentitiesexercisePetCommand.java

The package com.example.entitiesexercise is the folder path com/example/entitiesexercise 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.

  • entities-exercise/
    • src/main/
      • java/com/example/entitiesexercise/Package com.example.entitiesexercise
        • EntitiesExercisePlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • LuckyLootListener.javaListener: reacts to events
        • PetCommand.javayou are hereCommand (BasicCommand)
      • 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.

19player.getWorld().spawn(player.getLocation(), Wolf.class, wolf -> {20    wolf.setTamed(true);21    wolf.setOwner(player);22    wolf.setCollarColor(DyeColor.RED);23    wolf.customName(Component.text(player.getName() + "'s pet", NamedTextColor.AQUA));24});
  1. Makes the wolf tame. A tame wolf follows and defends its owner.
  2. Says whose pet it is. Without an owner a tamed wolf has nobody to follow.
  3. The collar color. DyeColor lists the sixteen dye colors.

Recap

  • Entities are everything that is not a block. The family tree (Entity, LivingEntity, Mob, Monster, Player) means abilities are inherited, and instanceof checks the kind.
  • world.spawn(location, Zombie.class, zombie -> { ... }) creates a mob and lets you customize it before anyone sees it.
  • Names, glowing, attributes (MAX_HEALTH, MOVEMENT_SPEED, ATTACK_DAMAGE), equipment, drop chances and AI switches shape a mob. After raising max health, call setHealth too.
  • setRemoveWhenFarAway controls despawning; setPersistent controls saving.
  • The getNearby... finders search a box, not a circle. Keep the box small.
  • EntityDamageEvent and EntityDeathEvent hear every entity: filter first. In a death event you can edit getDrops() and setDroppedExp.
  • launchProjectile throws a projectile; ProjectileHitEvent tells you what it hit; scoreboard tags mark your own entities.
  • remove() deletes silently; setHealth(0) or damage(...) kills with drops and a death event.

Quick quiz

  1. Why is world.spawn(location, Zombie.class, zombie -> { ... }) usually better than setting properties after spawning?

  2. You set Attribute.MAX_HEALTH to 100 on a new zombie but it dies after 20 damage. What did you forget?

  3. A player kills a custom boss and you want to hand out loot and a death message. Which way of getting rid of the boss would skip your EntityDeathEvent handler?

  4. world.getNearbyEntities(location, 10, 10, 10) returns an entity 12 blocks away in a corner. Why?

  5. What is the difference between setRemoveWhenFarAway(false) and setPersistent(true)?

Next steps