Entities and mobs
Spawn, customize, find and control mobs and other entities.
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.
These are the types you will meet most often:
| Type | What it adds | Examples |
|---|---|---|
Entity | A location, a velocity, a name, remove(), teleport() | Everything below |
LivingEntity | Health, potion effects, attributes, equipment, damage | Zombies, cows, players, armor stands |
Mob | A "brain" (AI) and a target to chase or follow | Zombies, cows, villagers |
Monster | Marks hostile mobs | Zombies, skeletons, creepers |
Animals | Marks passive farm and wild animals | Cows, sheep, wolves |
Player | A real person: inventory, game mode, chat | You |
Item | A dropped item lying on the ground | A dropped diamond |
Projectile | Something thrown or shot, with a shooter | Arrows, 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:
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}- A living entity is a kind of entity, so it promises everything an
Entitypromises, plus its ownhealth(). - A zombie is a
Monster, which is aLivingEntity, which is anEntity. That one line gives it all three sets of abilities. - This method accepts any entity at all: a zombie, a cow or a dropped item.
- 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 callhealth()on it. Thispattern 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:
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:
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
- java/com/example/entitiesmobs/Package com.example.entitiesmobs
- 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
- src/main/
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}- A simple command, exactly like the ones on Commands, part 1.
- Only a player has a position to spawn at. The console gets a friendly refusal.
- 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. - Spawns a zombie at that location. The third argument is the lambda.
- The lambda. Paper calls it with the brand-new zombie, named
zombiehere, before the zombie enters the world. Everything between the braces customizes it. - Gives the zombie a name. It takes a
Component(formatted text), so you can color it. See Text and colors with Adventure. - 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.
- Draws the colored outline you know from the glowing effect, even through walls. Great for finding a mob you just spawned.
- Makes the zombie a small, fast baby zombie. Older tutorials write
setBaby(true), which is deprecated;setBaby()is the modern call. - Stops the sun from setting the zombie on fire, so it survives until morning.
- 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
| Call | Effect |
|---|---|
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.
| Attribute | Controls | A normal zombie has |
|---|---|---|
Attribute.MAX_HEALTH | How many half-hearts the mob can have | 20 (ten hearts) |
Attribute.MOVEMENT_SPEED | How fast it walks | about 0.23 |
Attribute.ATTACK_DAMAGE | How hard it hits, in half-hearts | 3 |
Attribute.KNOCKBACK_RESISTANCE | How 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:
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
- java/com/example/entitiesmobs/Package com.example.entitiesmobs
- 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
- src/main/
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}- Triples the zombie's health ceiling. The helper method below does the actual change.
- Fills the new, bigger health bar. Without this line the tank would start with 20 health.
- A little slower than a normal zombie (0.23), which suits something heavy.
- Hits for six half-hearts instead of three.
- Gives the zombie armor and a weapon in a separate method, so this lambda stays easy to read.
- 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
falseto do the opposite. - 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.
- Some entities have no equipment. A zombie does, but checking costs one line and prevents a crash.
- Puts an iron helmet on the mob's head.
ItemStack.of(Material.IRON_HELMET)creates the item; see Items and ItemStacks. - 0.0 means "never drop this when the mob dies". The helmet is just decoration.
- 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.0fnever drops.1.0falways drops when a player killed the mob.- Above
1.0falways 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
| Call | What 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. Whentrue, the mob disappears if no player is near, like a random zombie wandering on the surface. Whenfalse, it stays loaded and counts as a permanent resident.setPersistent(boolean): saving. Whentrue(the default), the mob is written to the world file and is still there after a restart. Whenfalse, 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:
| Call | Returns | Use it when |
|---|---|---|
entity.getNearbyEntities(x, y, z) | Every entity in a box around that entity | You already have an entity in hand |
world.getNearbyEntities(location, x, y, z) | Every entity in a box around a spot | You have a location, not an entity |
world.getNearbyLivingEntities(location, radius) | Only living things | You do not care about dropped items and arrows |
world.getNearbyPlayers(location, radius) | Only players | A message or bar for everyone close |
world.getNearbyEntitiesByType(Monster.class, location, radius) | Only that class, already typed | You want just zombies, just monsters, and so on |
world.getEntitiesByClass(Cow.class) | Every cow in the whole world | A rare, whole-world check |
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:
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
- java/com/example/entitiesfind/Package com.example.entitiesfind
- 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
- src/main/
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}- One named number for the search size, so you change it in one place.
- 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. - Finds every living entity in the box around the player. Dropped items and arrows are skipped.
- Runs the loop body once for each creature found. See Loops.
- The finder includes the player at the center. This check skips them, so the list only shows others.
- The straight-line distance in blocks, which is what the player expects to read.
- The entity type as text, such as ZOMBIE.
- 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:
- COW 8.9 blocks away
- COW 12.4 blocks away
Damage and death events
Two events let you react to fights:
EntityDamageEventfires right before any entity takes damage, from a sword, a fall, fire, an explosion or anything else. You can readgetCause()(the kind of damage) andgetFinalDamage()(how much will really be taken after armor), or cancel it.EntityDamageByEntityEventis a more specific version that fires when something hit it. It addsgetDamager(). You can listen to either one.EntityDeathEventfires when a living entity dies. It tells you what will drop (getDrops()), how much experience (getDroppedExp()), and who did it (the dead entity'sgetKiller()).
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:
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
- java/com/example/entitiesdemo/Package com.example.entitiesdemo
- 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
- src/main/
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}- 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.
- The drops list is a normal editable list. Clearing it removes the usual rotten flesh and the equipment.
- Adds eight diamonds to what the zombie drops.
- Experience orbs worth 500 points fall, enough to level up a lot.
- The player who landed the final blow. It is
nullwhen 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.
| Class | What it is |
|---|---|
Snowball, Egg | Light throwables that push back what they hit |
Arrow, SpectralArrow, Trident | Shot or thrown weapons |
Fireball, SmallFireball, WitherSkull | Explosive or burning shots |
EnderPearl, ThrownPotion, ThrownExpBottle, WindCharge | Throwables with special effects |
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
- java/com/example/entitiesprojectiles/Package com.example.entitiesprojectiles
- 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
- src/main/
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}- A
Vectoris 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. - Creates a snowball that starts at the player and flies along the vector. The result is the snowball, so you can keep changing it.
- 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.
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
- java/com/example/entitiesprojectiles/Package com.example.entitiesprojectiles
- 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
- src/main/
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}- The event can be about any projectile, an arrow or a fireball. This keeps only snowballs and names the result
snowball. - Ignores ordinary snowballs. Only the ones tagged by
/snowblastpass. - Who threw it. A dispenser or a snow golem can shoot snowballs too, so the code makes sure it was a player.
- Checks that the thing it hit is a creature, not an item frame or a boat.
- Deals six half-hearts of damage and credits
shooterwith it. That credit means kill messages and the dead mob'sgetKiller()name the right player. - If no creature was hit, the snowball hit a block instead.
Splat. Your snowball hit STONE.
Removing entities
There are three ways to get rid of a mob, and they behave differently:
| Call | What happens | Death event? | Drops? |
|---|---|---|---|
entity.remove() | The entity vanishes instantly, with no animation. | No | No |
livingEntity.setHealth(0) | The mob dies as if it were killed. | Yes | Yes, the normal ones |
livingEntity.damage(1000, player) | The mob takes a huge hit and dies, credited to player. | Yes | Yes, 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(...).
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
- java/com/example/entitiesfind/Package com.example.entitiesfind
- 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
- src/main/
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}- Asks for only monsters near the player, so cows, villagers and dropped items are safe. The result is already typed as
Collection<Monster>. - Removes each monster instantly, without drops or a death event. That is what you want for a clean-up command.
- 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
- com/example/entitiesdemo/
- resources/
- plugin.ymlPlugin description and the entitiesdemo.boss permission
- java/
- main/
- src/
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.
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
- java/com/example/entitiesdemo/Package com.example.entitiesdemo
- 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
- src/main/
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}- The one and only manager. Both the listener and the command receive this same object.
- The listener gets the manager through its constructor, the special method that builds the object.
- 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.
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
- java/com/example/entitiesdemo/Package com.example.entitiesdemo
- 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
- src/main/
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}- Five blocks in front of the player, so the boss does not appear on top of them.
- 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.
- The boss must not despawn just because the player ran far away.
- Raises the maximum to 200 half-hearts, which is a hundred hearts.
- Players can hit the boss without it flying far across the room.
- Starts the fight at full health.
- 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.
- Remembers "this boss owns this bar" in a map. A
UUIDis a unique id every entity has, and it is the safe way to recognize an entity again later. - Finds every player within 40 blocks, because only they should see the bar.
- 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.
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
- java/com/example/entitiesdemo/Package com.example.entitiesdemo
- 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
- src/main/
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}- 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.
- Never lets the number go below zero, in case a huge hit overshoots.
- A boss bar fill is a number from 0 to 1, as a
float. The cast(float)converts the largerdouble.
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
- java/com/example/entitiesdemo/Package com.example.entitiesdemo
- 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
- src/main/
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}- The option
ignoreCancelled = trueskips hits that another plugin already canceled, so the bar only drops when the boss really takes damage. See Event priorities and canceling. - Moves the bar to match the new health.
getFinalDamage()is the amount after armor and resistance. - If the damage came from another entity, this unlocks
getDamager(). - 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.
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
- java/com/example/entitiesdemo/Package com.example.entitiesdemo
- 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
- src/main/
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}- The item itself, one nether star.
- Turns the default italic style off so the name looks like a real rare item.
- 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:
When the boss falls, everyone sees the announcement and the loot drops:
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
setHealthafterMAX_HEALTH. The mob keeps its old, smaller health. - Handling every entity.
EntityDamageEventandEntityDeathEventfire for all creatures in the world. Check the type and your own marker on the first lines, and return early. - Assuming
getKiller(),getHitEntity()orgetAttribute()always have a value. They can benull. 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
UUIDand look it up again, or checkisValid(). - 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.
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.
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
- java/com/example/entitiesexercise/Package com.example.entitiesexercise
- 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
- src/main/
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}- No player involved, so no bonus loot.
- A random decimal from 0 up to (but not including) 1. Below 0.2 happens one time in five.
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:
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
- java/com/example/entitiesexercise/Package com.example.entitiesexercise
- 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
- src/main/
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});- Makes the wolf tame. A tame wolf follows and defends its owner.
- Says whose pet it is. Without an owner a tamed wolf has nobody to follow.
- The collar color.
DyeColorlists 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, andinstanceofchecks 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, callsetHealthtoo. setRemoveWhenFarAwaycontrols despawning;setPersistentcontrols saving.- The
getNearby...finders search a box, not a circle. Keep the box small. EntityDamageEventandEntityDeathEventhear every entity: filter first. In a death event you can editgetDrops()andsetDroppedExp.launchProjectilethrows a projectile;ProjectileHitEventtells you what it hit; scoreboard tags mark your own entities.remove()deletes silently;setHealth(0)ordamage(...)kills with drops and a death event.
Quick quiz
Why is
world.spawn(location, Zombie.class, zombie -> { ... })usually better than setting properties after spawning?The lambda runs on the new entity before it is added to the world. Speed and despawning are not affected; those are separate settings.You set
Attribute.MAX_HEALTHto 100 on a new zombie but it dies after 20 damage. What did you forget?The attribute only raises the ceiling. The mob keeps its current health of 20 until you set it, in that order: attribute first, then health.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
EntityDeathEventhandler?remove()deletes the entity without a death, so no death event and no drops. Setting health to zero or damaging it makes a real death.world.getNearbyEntities(location, 10, 10, 10)returns an entity 12 blocks away in a corner. Why?The numbers are half the box size on each axis, so the corners reach about 17 blocks. Filter withdistance()if you need a true circle.What is the difference between
setRemoveWhenFarAway(false)andsetPersistent(true)?One controls despawning while the server runs, the other controls whether the mob is written to disk. A boss often needs the first without the second.
Next steps
- Potion effects, particles and effects: give your boss a glowing aura and lightning when it dies.
- Saving data on things: mark entities permanently so they survive restarts, a better tag than a map in memory.
- Timing and tasks: make a boss repeat an attack every few seconds.