Working with players
Find players, read and change their health, hunger, experience, game mode and more.
Players are the heart of almost every plugin. On this page you will learn how to find a player, how to read and change their health, hunger, experience, game mode and flying, and how to send them somewhere or remove them. At the end you will have a working /stats command and a listener that heals players when they level up.
What is a Player?
In your code, one connected person is one Player object. It is a handle to that person's character in the world. You can ask it questions ("how much health do you have?") and give it orders ("teleport to spawn"). Every method you call changes the real player on the real server, instantly.
Player is an interface, and it has a lot of methods because it builds on several smaller interfaces, one layer on top of the other. A player is an entity, so it has a location. It is a living entity, so it has health and potion effects. It is a human entity, so it has food and an inventory. IntelliJ shows all of them when you type player., which can feel like a flood. This picture tells you where to look:
You do not need to remember which layer a method lives in. The picture matters for one reason: when you search the Javadoc for getHealth and it is not on the Player page, it is not missing. It lives one layer down in Damageable, and player.getHealth() works anyway.
Getting a Player
Before you can do anything with a player, you need a Player object. Which code gets you one depends on what you already have:
From an event or a command
Most of the time the player is handed to you. Nearly every player event has getPlayer(), as you saw in Events and listeners. In a command, source.getSender() gives you whoever typed it. That sender might be the console, so a command that needs a player must check it first, as you learned in Commands, part 1.
By name
When someone types a name, such as /lookup Alex, you can find the matching online player with Bukkit.getPlayerExact(name). If that player is not online, the method returns null, which means "nothing here". Calling a method on null crashes your handler with a NullPointerException, so you must test for it. This command does exactly that:
Where this file livesplayers-lookupsrcmainjavacomexampleplayerslookupLookupCommand.java
The package com.example.playerslookup is the folder path com/example/playerslookup 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.
- players-lookup/
- src/main/
- java/com/example/playerslookup/Package com.example.playerslookup
- LookupCommand.javayou are hereCommand (BasicCommand)
- OnlineCommand.javaCommand (BasicCommand)
- PlayersLookupPlugin.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/playerslookup/Package com.example.playerslookup
- 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.playerslookup;2 3import io.papermc.paper.command.brigadier.BasicCommand;4import io.papermc.paper.command.brigadier.CommandSourceStack;5import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;6import org.bukkit.Bukkit;7import org.bukkit.OfflinePlayer;8import org.bukkit.command.CommandSender;9import org.bukkit.entity.Player;10 11public final class LookupCommand implements BasicCommand {12 13 @Override14 public void execute(CommandSourceStack source, String[] args) {15 CommandSender sender = source.getSender();16 if (args.length == 0) {17 sender.sendRichMessage("<red>Usage: /lookup (name)");18 return;19 }20 21 String name = args[0];22 Player online = Bukkit.getPlayerExact(name);23 if (online != null) {24 sender.sendRichMessage("<green><name> is online right now, in world <world>.",25 Placeholder.unparsed("name", online.getName()),26 Placeholder.unparsed("world", online.getWorld().getName()));27 return;28 }29 30 OfflinePlayer known = Bukkit.getOfflinePlayerIfCached(name);31 if (known != null && known.hasPlayedBefore()) {32 sender.sendRichMessage("<yellow><name> has joined before, but is offline.",33 Placeholder.unparsed("name", name));34 } else {35 sender.sendRichMessage("<red>Nobody called <name> has joined this server.",36 Placeholder.unparsed("name", name));37 }38 }39}- The player typed just
/lookupwith no name. Always handle a missing argument before you readargs[0], or you get anArrayIndexOutOfBoundsException. - Looks for an online player with exactly this name (capital letters do not matter). It returns a
Playerornull. - The check that protects you. Inside this block
onlineis guaranteed to be a real player. - Every player knows which world they are in. A world has a name, like
worldorworld_nether. - Stops the method here. Without it, the code below would also run and tell the sender that the player never joined.
- Looks for a player who is not online. It only knows players the server has seen recently, so it never waits for the internet. It returns
nullwhen it does not know the name. - True when this server has saved data for the player, which means they joined at least once.
Notch has joined before, but is offline.
Nobody called Zork has joined this server.
Everyone who is online
Bukkit.getOnlinePlayers() returns every online player. You walk through them with a for loop (see Loops). Inside the loop, the variable holds one player at a time. This command lists everybody:
Where this file livesplayers-lookupsrcmainjavacomexampleplayerslookupOnlineCommand.java
The package com.example.playerslookup is the folder path com/example/playerslookup 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.
- players-lookup/
- src/main/
- java/com/example/playerslookup/Package com.example.playerslookup
- LookupCommand.javaCommand (BasicCommand)
- OnlineCommand.javayou are hereCommand (BasicCommand)
- PlayersLookupPlugin.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/playerslookup/Package com.example.playerslookup
- 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.playerslookup;2 3import io.papermc.paper.command.brigadier.BasicCommand;4import io.papermc.paper.command.brigadier.CommandSourceStack;5import java.util.ArrayList;6import java.util.List;7import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;8import org.bukkit.Bukkit;9import org.bukkit.entity.Player;10 11public final class OnlineCommand implements BasicCommand {12 13 @Override14 public void execute(CommandSourceStack source, String[] args) {15 List<String> names = new ArrayList<>();16 for (Player player : Bukkit.getOnlinePlayers()) {17 names.add(player.getName());18 }19 20 source.getSender().sendRichMessage("<gold>Online (<count>): <white><names>",21 Placeholder.unparsed("count", String.valueOf(names.size())),22 Placeholder.unparsed("names", String.join(", ", names)));23 }24}- A list of text, filled in the loop. A list is like a chest that can grow as you add things.
- Read it as "for each player in the online players". The body runs once per player, with
playerset to the next one each time. - Saves this player's name in the list.
- Glues all names into one text with a comma and a space between them.
By UUID
Every player has a UUID, a long unique id such as 069a79f4-44e9-4726-a5be-fca90e38aaf5. It never changes, even when the player changes their Minecraft name, which is why plugins save UUIDs instead of names. player.getUniqueId() gives you the UUID, and Bukkit.getPlayer(uuid) turns it back into a Player (or null when that person is offline). The time, UUID and random chapter explains UUIDs in more detail.
Players who are offline
A Player only exists while that person is online. For somebody who is offline, Paper uses a smaller type called OfflinePlayer. It can answer only a few questions: getName(), getUniqueId(), hasPlayedBefore(), isOnline() and when they last played. Everything else (health, teleporting, messages) needs a real Player.
Every Player is also an OfflinePlayer, so a method that accepts an OfflinePlayer accepts online players too. To go the other way, ask an OfflinePlayer for getPlayer(). You get the online Player, or null when they are away.
Names: who is this player?
A player has several kinds of name, and they are easy to mix up:
| Method | Gives you | Use it for |
|---|---|---|
getName() | The real account name as plain text, such as Steve | Commands, logs, matching text |
name() | The same name as a Component | Putting the name inside formatted messages |
displayName() | The nickname used in chat, which plugins can change | Chat lines and messages meant for people |
playerListName() | The name in the Tab player list | Rank prefixes and colors in Tab |
getUniqueId() | The UUID | Saving data about the player |
The setters take a Component, Paper's type for formatted text (see Text and colors with Adventure):
Where this file livesplayers-snippetssrcmainjavacomexampleplayerssnippetsPlayerSnippets.java
The package com.example.playerssnippets is the folder path com/example/playerssnippets 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.
- players-snippets/
- src/main/java/com/example/playerssnippets/Package com.example.playerssnippets
- PlayerSnippets.javayou are hereHelper 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
- src/main/java/com/example/playerssnippets/Package com.example.playerssnippets
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.
16void identity(Player player) {17 String plainName = player.getName();18 UUID id = player.getUniqueId();19 Component fancyName = Component.text("Captain " + plainName, NamedTextColor.GOLD);20 player.displayName(fancyName);21 player.playerListName(fancyName);22}- Plain text. It never changes while the player is online, no matter what nickname they have.
- Changes the name other plugins and the chat use for this player. The player's real name stays the same.
- Changes only the entry in the Tab list. Passing
nullputs the normal name back.
Health and food
Health is a number of half hearts. A full bar is 20.0, which is 10 hearts. So getHealth() / 2 gives hearts, and 0 means dead.
Where this file livesplayers-snippetssrcmainjavacomexampleplayerssnippetsPlayerSnippets.java
The package com.example.playerssnippets is the folder path com/example/playerssnippets 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.
- players-snippets/
- src/main/java/com/example/playerssnippets/Package com.example.playerssnippets
- PlayerSnippets.javayou are hereHelper 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
- src/main/java/com/example/playerssnippets/Package com.example.playerssnippets
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.
24void health(Player player) {25 double hearts = player.getHealth() / 2;26 player.setHealth(10.0);27 player.heal(4.0);28 player.setFireTicks(0);29}- Half hearts divided by two is hearts. Dividing by
2here works becausegetHealth()returns adouble, so it keeps the decimals. - Sets health to exactly 5 hearts, whatever it was before.
- Adds 2 hearts. Unlike
setHealth, this fires an event so other plugins can react to the healing. - Puts out fire. Fire time is counted in
ticks (20 per second), so200would burn the player for 10 seconds.
The maximum health trap
setHealth throws an IllegalArgumentException when the number is below 0 or above the player's maximum health. The maximum is 20 for most players, but other plugins, potions and armor can change it. Read it from the attribute that stores it, Attribute.MAX_HEALTH:
Where this file livesplayers-snippetssrcmainjavacomexampleplayerssnippetsPlayerSnippets.java
The package com.example.playerssnippets is the folder path com/example/playerssnippets 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.
- players-snippets/
- src/main/java/com/example/playerssnippets/Package com.example.playerssnippets
- PlayerSnippets.javayou are hereHelper 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
- src/main/java/com/example/playerssnippets/Package com.example.playerssnippets
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.
31void maxHealth(Player player) {32 AttributeInstance maxHealth = player.getAttribute(Attribute.MAX_HEALTH);33 if (maxHealth != null) {34 maxHealth.setBaseValue(40.0);35 player.setHealth(maxHealth.getValue());36 }37}- An attribute is a number on a living thing, like max health, movement speed or attack damage. This asks the player for the max health attribute.
- The method could return
null(some entities have no such attribute), so the check is required. - Gives the player 20 hearts. The base value is the number before bonuses from potions and armor are added.
- The final maximum including every bonus. Using it makes
setHealthsafe, because the number can never be too big.
Food works with three numbers that you can see in the hunger bar and one that you cannot:
- Food level, 0 to 20: the drumsticks.
setFoodLevel(20)fills the bar. - Saturation: a hidden buffer. While it is above 0, the food level does not drop. Golden carrots and steak give lots of saturation, which is why they keep you full longer.
- Exhaustion: hidden. Running, jumping and fighting fill it, and when it is full it takes away saturation first, then food.
Where this file livesplayers-snippetssrcmainjavacomexampleplayerssnippetsPlayerSnippets.java
The package com.example.playerssnippets is the folder path com/example/playerssnippets 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.
- players-snippets/
- src/main/java/com/example/playerssnippets/Package com.example.playerssnippets
- PlayerSnippets.javayou are hereHelper 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
- src/main/java/com/example/playerssnippets/Package com.example.playerssnippets
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.
39void food(Player player) {40 player.setFoodLevel(20);41 player.setSaturation(5.0f);42 player.setExhaustion(0.0f);43}- A full hunger bar. The bar only has 20 points, so keep your numbers between 0 and 20.
- The
fafter the number makes it afloat, the number type this method expects. Withoutf, Java complains because5.0is adouble.
Experience
The green bar above the hotbar has two parts that you can set separately: the level (the number) and the progress toward the next level (how full the bar is).
Where this file livesplayers-snippetssrcmainjavacomexampleplayerssnippetsPlayerSnippets.java
The package com.example.playerssnippets is the folder path com/example/playerssnippets 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.
- players-snippets/
- src/main/java/com/example/playerssnippets/Package com.example.playerssnippets
- PlayerSnippets.javayou are hereHelper 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
- src/main/java/com/example/playerssnippets/Package com.example.playerssnippets
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.
45void experience(Player player) {46 player.setLevel(30);47 player.setExp(0.5f);48 player.giveExp(100);49 int total = player.getTotalExperience();50}- Shows level 30. The progress bar stays where it was.
- Progress is a fraction from 0.0 (empty bar) to 1.0 (about to level up), not a number of points.
0.5fmeans the bar is half full. - Adds 100 experience points, as if the player collected orbs. Levels go up on their own when the bar overflows.
- Every point the player ever collected. It is a separate counter, so do not use it to work out the level.
Game mode, flying and speed
The GameMode enum lists the four modes: SURVIVAL, CREATIVE, ADVENTURE and SPECTATOR. An enum is a fixed list of choices, so you cannot misspell one.
Where this file livesplayers-snippetssrcmainjavacomexampleplayerssnippetsPlayerSnippets.java
The package com.example.playerssnippets is the folder path com/example/playerssnippets 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.
- players-snippets/
- src/main/java/com/example/playerssnippets/Package com.example.playerssnippets
- PlayerSnippets.javayou are hereHelper 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
- src/main/java/com/example/playerssnippets/Package com.example.playerssnippets
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.
52void gameMode(Player player) {53 player.setGameMode(GameMode.CREATIVE);54 GameMode now = player.getGameMode();55}- Switches the player's game mode immediately, the same as
/gamemode creative. - Reads the current mode. Compare it with
==, for exampleplayer.getGameMode() == GameMode.SURVIVAL.
Flying is two separate switches. Allow flight decides whether double-tapping jump starts flying (this is what makes creative mode work). Flying says whether the player is in the air right now. Speed is a number from -1 to 1; the normal walking speed is 0.2 and the normal flying speed is 0.1.
Where this file livesplayers-snippetssrcmainjavacomexampleplayerssnippetsPlayerSnippets.java
The package com.example.playerssnippets is the folder path com/example/playerssnippets 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.
- players-snippets/
- src/main/java/com/example/playerssnippets/Package com.example.playerssnippets
- PlayerSnippets.javayou are hereHelper 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
- src/main/java/com/example/playerssnippets/Package com.example.playerssnippets
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.
57void flying(Player player) {58 player.setAllowFlight(true);59 player.setFlying(true);60 player.setFlySpeed(0.2f);61 player.setWalkSpeed(0.3f);62}- Lets the player take off by double-tapping the jump key, even in survival mode. Without it, flying cannot be started: call this before
setFlying(true). - Starts the player flying right now, without waiting for the double tap.
- Doubles the normal flying speed. Numbers above 1 or below -1 throw an
IllegalArgumentException. - Walking speed, one and a half times the normal 0.2.
Teleporting and spawn points
player.teleport(location) moves a player at once. A Location is a world plus coordinates; the next chapter shows how to build and change them. Here you only need the two locations every player has: the world's spawn point and their own respawn point (their bed or respawn anchor).
Where this file livesplayers-snippetssrcmainjavacomexampleplayerssnippetsPlayerSnippets.java
The package com.example.playerssnippets is the folder path com/example/playerssnippets 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.
- players-snippets/
- src/main/java/com/example/playerssnippets/Package com.example.playerssnippets
- PlayerSnippets.javayou are hereHelper 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
- src/main/java/com/example/playerssnippets/Package com.example.playerssnippets
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.
64void teleporting(Player player) {65 Location spawn = player.getWorld().getSpawnLocation();66 player.teleport(spawn);67 player.teleportAsync(spawn).thenAccept(success -> {68 if (success) {69 player.sendRichMessage("<green>You arrived!");70 }71 });72}- The spawn point of the world the player is standing in.
- Moves the player and returns
trueon success. If the target chunk is not loaded yet, the server loads it first and may pause for a moment. - Loads the target chunk in the background and then teleports, so the server does not pause. It returns a result that arrives later, which is why you add the follow-up with
thenAccept. - A
lambda : a small piece of code that runs once the teleport has finished.successistruewhen it worked, so theifsends the message only then. Lambdas explains the arrow.
Where this file livesplayers-snippetssrcmainjavacomexampleplayerssnippetsPlayerSnippets.java
The package com.example.playerssnippets is the folder path com/example/playerssnippets 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.
- players-snippets/
- src/main/java/com/example/playerssnippets/Package com.example.playerssnippets
- PlayerSnippets.javayou are hereHelper 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
- src/main/java/com/example/playerssnippets/Package com.example.playerssnippets
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.
74void bedSpawn(Player player) {75 Location bed = player.getRespawnLocation();76 if (bed != null) {77 player.teleport(bed);78 }79}- Where the player would respawn: their bed or anchor. It is
nullwhen they have none, or when the bed was broken. - Skip the teleport when there is no bed. Teleporting to
nullwould crash.
Kicking, permissions and operators
player.kick(component) disconnects the player and shows your message on the "Disconnected" screen. Write a helpful reason, because players read it.
Where this file livesplayers-snippetssrcmainjavacomexampleplayerssnippetsPlayerSnippets.java
The package com.example.playerssnippets is the folder path com/example/playerssnippets 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.
- players-snippets/
- src/main/java/com/example/playerssnippets/Package com.example.playerssnippets
- PlayerSnippets.javayou are hereHelper 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
- src/main/java/com/example/playerssnippets/Package com.example.playerssnippets
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.
81void kicking(Player player) {82 player.kick(Component.text("Come back in five minutes!", NamedTextColor.RED));83}- The player is thrown back to the server list and sees this red message. Calling
kick()without anything uses the default message.
To decide what a player is allowed to do, use player.hasPermission("myplugin.admin"). That returns true or false and is the right check for almost everything. player.isOp() asks whether the player is a server operator. Operators automatically have most permissions, but checking isOp() directly locks out admins who use a permissions plugin, so avoid it. The Permissions chapter goes deeper.
What the player is doing right now
Where this file livesplayers-snippetssrcmainjavacomexampleplayerssnippetsPlayerSnippets.java
The package com.example.playerssnippets is the folder path com/example/playerssnippets 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.
- players-snippets/
- src/main/java/com/example/playerssnippets/Package com.example.playerssnippets
- PlayerSnippets.javayou are hereHelper 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
- src/main/java/com/example/playerssnippets/Package com.example.playerssnippets
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.
90void state(Player player) {91 boolean sneaking = player.isSneaking();92 boolean sprinting = player.isSprinting();93 Location where = player.getLocation();94 World world = player.getWorld();95 ItemStack inHand = player.getInventory().getItemInMainHand();96}- True while the player holds Shift.
- True while the player is sprinting.
- The exact spot where the player stands, including the direction they look. It is a fresh copy, so changing it does not move the player.
- The item in the selected hotbar slot. When the hand is empty you get an item of type air, not
null. See Items and ItemStacks and Inventories.
Demo plugin: /stats and heal on level up
Time to combine everything. This plugin has two features: /stats shows information about you (or about another online player), and a listener that fully heals and feeds players whenever their level goes up. First the command:
Where this file livesplayers-demosrcmainjavacomexampleplayersdemoStatsCommand.java
The package com.example.playersdemo is the folder path com/example/playersdemo 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.
- players-demo/
- src/main/
- java/com/example/playersdemo/Package com.example.playersdemo
- LevelUpHealListener.javaListener: reacts to events
- PlayersDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
- StatsCommand.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/playersdemo/Package com.example.playersdemo
- 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.playersdemo;2 3import io.papermc.paper.command.brigadier.BasicCommand;4import io.papermc.paper.command.brigadier.CommandSourceStack;5import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;6import org.bukkit.Bukkit;7import org.bukkit.attribute.Attribute;8import org.bukkit.attribute.AttributeInstance;9import org.bukkit.command.CommandSender;10import org.bukkit.entity.Player;11 12public final class StatsCommand implements BasicCommand {13 14 @Override15 public void execute(CommandSourceStack source, String[] args) {16 CommandSender sender = source.getSender();17 Player target;18 19 if (args.length > 0) {20 target = Bukkit.getPlayerExact(args[0]);21 if (target == null) {22 sender.sendRichMessage("<red>Nobody called <name> is online.",23 Placeholder.unparsed("name", args[0]));24 return;25 }26 } else if (sender instanceof Player self) {27 target = self;28 } else {29 sender.sendRichMessage("<red>The console has no stats. Use /stats (name) instead.");30 return;31 }32 33 showStats(sender, target);34 }35 36 private void showStats(CommandSender sender, Player target) {37 double maxHealth = 20.0;38 AttributeInstance maxHealthAttribute = target.getAttribute(Attribute.MAX_HEALTH);39 if (maxHealthAttribute != null) {40 maxHealth = maxHealthAttribute.getValue();41 }42 43 sender.sendRichMessage("<gold>--- Stats for <white><name></white> ---",44 Placeholder.unparsed("name", target.getName()));45 sender.sendRichMessage("<red>Health: <white><health> / <max>",46 Placeholder.unparsed("health", String.valueOf(Math.round(target.getHealth()))),47 Placeholder.unparsed("max", String.valueOf(Math.round(maxHealth))));48 sender.sendRichMessage("<gold>Food: <white><food> / 20",49 Placeholder.unparsed("food", String.valueOf(target.getFoodLevel())));50 sender.sendRichMessage("<green>Level: <white><level>",51 Placeholder.unparsed("level", String.valueOf(target.getLevel())));52 sender.sendRichMessage("<aqua>Game mode: <white><mode>",53 Placeholder.unparsed("mode", target.getGameMode().name().toLowerCase()));54 sender.sendRichMessage("<light_purple>Flying: <white><flying>",55 Placeholder.unparsed("flying", target.isFlying() ? "yes" : "no"));56 }57}- A variable that must be filled in on every path below. It will hold the player whose stats we show.
- The sender typed a name after the command, like
/stats Alex. - That name is not online. Tell the sender and leave, using
return. - Asks "is the sender a player?" and, if so, saves it as
self. A console is not a player, so it takes the last branch. - By here
targetis always a real player, so the work moves into its own method. - A safe starting value, replaced below by the real maximum when the attribute exists.
- Rounds half hearts to a whole number, so the chat shows
20and not20.0. - Turns
SURVIVALintosurvival, text that reads better in chat. - A one-line choice: if
isFlying()is true use"yes", otherwise"no".
Health: 18 / 20
Food: 20 / 20
Level: 7
Game mode: survival
Flying: no
Now the listener. It uses PlayerLevelChangeEvent, which fires for every level change, including going down when a player spends levels at an enchanting table. The first check makes sure we only reward real level-ups:
Where this file livesplayers-demosrcmainjavacomexampleplayersdemoLevelUpHealListener.java
The package com.example.playersdemo is the folder path com/example/playersdemo 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.
- players-demo/
- src/main/
- java/com/example/playersdemo/Package com.example.playersdemo
- LevelUpHealListener.javayou are hereListener: reacts to events
- PlayersDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
- StatsCommand.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/playersdemo/Package com.example.playersdemo
- 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.playersdemo;2 3import org.bukkit.attribute.Attribute;4import org.bukkit.attribute.AttributeInstance;5import org.bukkit.entity.Player;6import org.bukkit.event.EventHandler;7import org.bukkit.event.Listener;8import org.bukkit.event.player.PlayerLevelChangeEvent;9 10public final class LevelUpHealListener implements Listener {11 12 @EventHandler13 public void onLevelChange(PlayerLevelChangeEvent event) {14 if (event.getNewLevel() <= event.getOldLevel()) {15 return;16 }17 18 Player player = event.getPlayer();19 AttributeInstance maxHealth = player.getAttribute(Attribute.MAX_HEALTH);20 if (maxHealth == null) {21 return;22 }23 24 player.setHealth(maxHealth.getValue());25 player.setFoodLevel(20);26 player.sendRichMessage("<green>Level up! You feel fully rested.");27 }28}- If the new level is not higher than the old one, the player lost or kept their level. A guard clause that stops early.
- Reads the real maximum health, so the heal also works for players with extra hearts.
- A full heal. It is safe because the number equals the maximum, never above it.
- Fills the hunger bar too.
Where this file livesplayers-demosrcmainjavacomexampleplayersdemoPlayersDemoPlugin.java
The package com.example.playersdemo is the folder path com/example/playersdemo 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.
- players-demo/
- src/main/
- java/com/example/playersdemo/Package com.example.playersdemo
- LevelUpHealListener.javaListener: reacts to events
- PlayersDemoPlugin.javayou are hereMain class: Paper starts here (named as main in plugin.yml)
- StatsCommand.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/playersdemo/Package com.example.playersdemo
- 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.playersdemo;2 3import org.bukkit.plugin.java.JavaPlugin;4 5public final class PlayersDemoPlugin extends JavaPlugin {6 7 @Override8 public void onEnable() {9 registerCommand("stats", "Shows health, food, level and game mode", new StatsCommand());10 getServer().getPluginManager().registerEvents(new LevelUpHealListener(), this);11 }12}- Adds the
/statscommand with a short description. - Registers the listener. Without this line, level-ups would do nothing.
- players-demo/
- src/
- main/
- java/
- com/example/playersdemo/
- PlayersDemoPlugin.javaThe main class: registers the command and the listener
- StatsCommand.javaThe /stats command
- LevelUpHealListener.javaHeals and feeds players who gain a level
- com/example/playersdemo/
- resources/
- plugin.ymlPlugin name, version and main class
- java/
- main/
- src/
You can download the whole demo as a Gradle project, run it on your test server, and then try /xp add Steve 1 levels to watch the heal happen. The small lookup commands from earlier are in another download.
Mistakes to avoid
- Using the result of
getPlayerExactwithout a null check. It isnullwhenever the person is offline or misspelled the name. - Setting health above the maximum.
setHealth(100)throws anIllegalArgumentExceptionfor a normal player. UseAttribute.MAX_HEALTHto find the limit. - Keeping a
Playerin a field for hours. Save the UUID instead, and look the player up when you need them. - Comparing players by name. Use the UUID. Names change and can differ in capital letters.
- Confusing experience progress with points.
setExp(50)is wrong; progress is a fraction between 0 and 1. UsegiveExp(50)to add points. - Setting flying without allowing flight. Call
setAllowFlight(true)first; flying cannot start without it. - Old text methods.
setDisplayName(String)andkickPlayer(String)are deprecated. UsedisplayName(Component)andkick(Component).
A /fly toggle
Write a /fly command that turns flying on for the player who typed it and turns it off if they type it again. Tell them which one happened. Players only: the console should get a polite refusal.
Hint 1
Start from StatsCommand: the instanceof Player check is exactly what you need to reject the console.
Hint 2
Read player.getAllowFlight(), flip it with !, and write the result back with setAllowFlight.
Show the solution
The command stores the opposite of the current setting in canFlyNow and applies it. Turning allow-flight off also ends any flying in progress, so no extra code is needed.
Where this file livesplayers-exercisesrcmainjavacomexampleplayersexerciseFlyCommand.java
The package com.example.playersexercise is the folder path com/example/playersexercise 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.
- players-exercise/
- src/main/
- java/com/example/playersexercise/Package com.example.playersexercise
- FlyCommand.javayou are hereCommand (BasicCommand)
- PlayersExercisePlugin.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/playersexercise/Package com.example.playersexercise
- 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.playersexercise;2 3import io.papermc.paper.command.brigadier.BasicCommand;4import io.papermc.paper.command.brigadier.CommandSourceStack;5import org.bukkit.entity.Player;6 7public final class FlyCommand implements BasicCommand {8 9 @Override10 public void execute(CommandSourceStack source, String[] args) {11 if (!(source.getSender() instanceof Player player)) {12 source.getSender().sendRichMessage("<red>Only players can fly.");13 return;14 }15 16 boolean canFlyNow = !player.getAllowFlight();17 player.setAllowFlight(canFlyNow);18 19 if (canFlyNow) {20 player.sendRichMessage("<green>Flying is on. Double-tap jump to take off.");21 } else {22 player.sendRichMessage("<yellow>Flying is off.");23 }24 }25}- Reads "if the sender is NOT a player". The
playervariable is available after this block, because the block always returns. - The
!flipstruetofalseand back, so each use toggles.
Add ping to /stats
Players have a method that returns their connection delay in milliseconds. Find it with autocomplete, then add a line to /stats that shows it, for example "Ping: 42 ms".
Hint
Type target.get inside showStats and scroll the list, or look for a method with "Ping" in its name.
Show the solution
The method is getPing(), and it returns an int. Add one more line to showStats, written the same way as the others:
1sender.sendRichMessage("<yellow>Ping: <white><ping> ms",2 Placeholder.unparsed("ping", String.valueOf(target.getPing())));Recap
- A
Playeris one connected person. You get one from an event, a command sender,Bukkit.getPlayerExact(name),Bukkit.getPlayer(uuid)orBukkit.getOnlinePlayers(). - Lookups by name or UUID return
nullwhen nobody matches. Always check before you use the result. - An
OfflinePlayeronly knows name, UUID and play history. Save UUIDs, not names orPlayerobjects. - Health is in half hearts and must stay between 0 and
Attribute.MAX_HEALTH. Food level is 0 to 20, and experience progress is a fraction from 0 to 1. - Game mode, allow-flight, flying and speeds each have a getter and a setter.
teleportmoves a player at once,teleportAsyncloads the target chunk without pausing the server, andkick(Component)removes someone with a message.
Quick quiz
A player types
/lookup Zorkbut nobody called Zork is online. What doesBukkit.getPlayerExact("Zork")return?It returnsnull, meaning "no such online player". Using that value as a player would crash your handler, so check!= nullfirst.What should a plugin save to remember a player between logins?
ThePlayerobject goes stale when the player logs out, and names can change. The UUID never changes, and you can turn it back into aPlayerwithBukkit.getPlayer(uuid).Why is
player.setHealth(100)a bug on a normal server?Health must stay between 0 and the maximum, which is 20 for most players. Read the real limit withplayer.getAttribute(Attribute.MAX_HEALTH).What does
player.setExp(0.5f)do?setExpsets the bar's progress as a fraction from 0 to 1. To add points usegiveExp, and to change the level usesetLevel.You call
player.setFlying(true)on a survival player but flying does not work. What is missing?A player may only fly when flight is allowed. Switching to creative also works, but it changes much more than you wanted.setAllowFlight(true)is the precise switch.
Next steps
- Worlds, locations and movement: where players are, how to build a
Location, and how to launch them through the air. - Items and ItemStacks and Inventories: what players carry.
- Permissions: decide who may use your commands.
- Project: /heal and /feed with cooldowns: build the health and food ideas from this page into a real feature.