Variables and types
Store numbers, text and true/false values, and learn which type fits which job.
Plugins are full of values: a player's health, their coins, their name, whether they are flying. On this page you will learn how Java stores values in variables, which type fits which job, how to turn text into numbers, and you will build a plugin with /mystats and /setfood commands.
What a variable is
A variable is a named place that holds one value. You give it a name, put a value in, read the value later, and replace it with a new value whenever you like.
Here is a variable in action. Run it, then read the notes:
1void main() {2 int coins = 50;3 IO.println("You start with " + coins + " coins.");4 5 coins = 80;6 IO.println("After selling wheat: " + coins + " coins.");7 8 coins = coins + 25;9 IO.println("After a quest reward: " + coins + " coins.");10}- Creates a variable.
intis the type (a whole number),coinsis the name, and50is the first value put inside. Creating a variable is calleddeclaring it. - Using the name
coinsreads whatever is inside the chest right now: 50. - Puts a new value in. The old 50 is gone. No
intthis time, because the variable already exists; you only write the type once, when you create it. - Read this right to left: take what is in
coins(80), add 25, and put the answer (105) back intocoins. - Prints the newest value.
Every declaration has the same parts:
| Part | Example | Meaning |
|---|---|---|
| Type | int | What kind of value fits inside. |
| Name | coins | The sign on the chest. camelCase, starting with a small letter. |
= | = | "Put this value into the variable." It is not "equals" like in math class. |
| Value | 50 | What goes in. It can be a plain value or a calculation. |
; | ; | Ends the statement, as always. |
Declaring first, filling later, and copying
You can create a variable without a value and fill it later. And when you put one variable into another, Java copies the value, so the two chests are independent afterwards:
1void main() {2 int kills;3 kills = 3;4 5 int bestKills = kills;6 kills = kills + 1;7 8 IO.println("Kills now: " + kills);9 IO.println("Best before this fight: " + bestKills);10}- Declares an empty
intvariable. It has no value yet, so you may not read it until you assign one. - The first value goes in. This is called
assigning a value. - Copies the 3 into a second chest. The two variables do not stay linked.
- Changes only
kills, to 4.bestKillsstill holds 3, as the output shows.
Variables in plugin code
Plugins use variables constantly, mostly to hold answers the server gives you. Each line below asks the server for something and keeps the answer in a variable with the right type:
1Player player = event.getPlayer();2String name = player.getName();3double health = player.getHealth();4int foodLevel = player.getFoodLevel();5boolean flying = player.isFlying();- The type here is
Player, a Paper type for an online player. Variables can hold game things too, not only numbers and text. - The player's name, as text.
- Health is a number with decimals, like
17.5. - The hunger bar is a whole number from 0 to 20.
- Flying or not:
trueorfalse.
Why store the answer instead of asking again each time? It makes the next lines shorter, it gives the value a clear name, and you know the value will not change halfway through your code.
Types: which chest for which value
Java needs to know each variable's type before the program runs. That lets the compiler catch mistakes early: if you try to put text into a number variable, your code does not even compile. Java has eight basic types built in, called primitive types. They are all written in lowercase:
| Type | Holds | Range | Minecraft examples |
|---|---|---|---|
int | Whole numbers | about -2.1 billion to 2.1 billion | Coins, food level, XP level, kills, slot numbers, block coordinates |
long | Big whole numbers | about -9.2 quintillion to 9.2 quintillion | Times in milliseconds, world seeds, huge balances |
double | Numbers with decimals | huge, about 15 to 16 correct digits | Health, exact coordinates (x, y, z), damage, money with cents |
float | Numbers with decimals, less precise | about 7 correct digits | Head direction (yaw, pitch), saturation, walk speed, XP bar progress |
boolean | true or false | only those two | Is flying, is sneaking, has played before, is enabled |
char | One single character | one letter, digit or symbol | Rarely used in plugins |
byte, short | Small whole numbers | -128 to 127, -32,768 to 32,767 | Rarely used in plugins; you may see byte in saved data |
Here are most of them at once, holding a player's stats:
1void main() {2 String name = "Steve";3 double health = 20.0;4 int foodLevel = 20;5 int coins = 1250;6 float walkSpeed = 0.2f;7 boolean isFlying = false;8 long playTimeMillis = 7_200_000L;9 char rankLetter = 'A';10 11 IO.println("Name: " + name);12 IO.println("Health: " + health);13 IO.println("Food: " + foodLevel);14 IO.println("Coins: " + coins);15 IO.println("Walk speed: " + walkSpeed);16 IO.println("Flying: " + isFlying);17 IO.println("Play time (ms): " + playTimeMillis);18 IO.println("Rank: " + rankLetter);19}- Text.
Stringis not a primitive type (notice the capital S); it gets its own section below. - A full health bar is 20.0 health points, which is 10 hearts. Writing
.0makes the value adouble. - A full hunger bar.
- The
fat the end marks the value as afloat. Without it, Java treats0.2as adoubleand refuses to squeeze it into afloatvariable. Paper'splayer.getWalkSpeed()also returns afloat, from -1 to 1. - Only
trueorfalse, written without quotes. - Two hours in milliseconds. The
Lmarks alongvalue. The underscores are just for your eyes, like the commas in 7,200,000; Java ignores them. - A single character goes in single quotes. Double quotes would make it a
String.
Want to see the exact limits? Each number type has a MIN_VALUE and MAX_VALUE you can print:
1void main() {2 IO.println("byte: " + Byte.MIN_VALUE + " to " + Byte.MAX_VALUE);3 IO.println("short: " + Short.MIN_VALUE + " to " + Short.MAX_VALUE);4 IO.println("int: " + Integer.MIN_VALUE + " to " + Integer.MAX_VALUE);5 IO.println("long: " + Long.MIN_VALUE + " to " + Long.MAX_VALUE);6 IO.println("double can hold up to about " + Double.MAX_VALUE);7}- The smallest and largest
int. Each primitive type has a matching class with a capital letter (Integerforint,Long,Double, and so on) that holds useful constants and helpers. - The
E308means "times 10 to the power 308". You will never run out.
Why health is a double and coins an int
Choose the type by asking: "Can this value have decimals?"
- Health can. A zombie hit can leave you at 17.5 health, and armor reduces damage by fractions of a point. So health is a
double. - Coins usually cannot. Nobody owns 12.75 diamonds. Whole numbers are simpler and exact, so coins, levels and item amounts are
int. - Yes-or-no questions are always
boolean. Do not use the numbers 0 and 1 for that. - Times in milliseconds are huge numbers, far beyond 2.1 billion, so they need a
long.
In plugins, you rarely choose: Paper already decided. Every method tells you what type it gives back, and your variable must match. Hover a method in IntelliJ, or press Ctrl+Q, to see its type. These are the types Paper uses for common player values:
| Paper method | Returns | Example value |
|---|---|---|
player.getName() | String | "Steve" |
player.getHealth() | double | 17.5 |
player.getFoodLevel() | int | 18 |
player.getSaturation() | float | 3.2 |
player.getLevel() | int | 12 |
player.getExp() | float | 0.45 (progress to the next level, 0 to 1) |
player.isFlying() | boolean | false |
player.getFirstPlayed() | long | milliseconds since January 1, 1970 |
player.getLocation().getX() | double | 104.37 |
player.getLocation().getBlockX() | int | 104 |
player.getLocation().getYaw() | float | -90.0 |
Notice the naming habit: methods that answer a yes-or-no question start with is or has (isFlying, hasPlayedBefore) and return a boolean. Methods that give a value start with get. Name your own variables the same way: isFlying, hasKit, coins.
String: text
Text is stored in a String (a "string of characters"). A String value is written between double quotes. Unlike the primitive types, String starts with a capital S, because it is a class: a richer type that comes with its own methods, such as length().
1void main() {2 String name = "Alex";3 String greeting = "Welcome back, " + name + "!";4 String nickname = null;5 6 IO.println(greeting);7 IO.println("Letters in the name: " + name.length());8 IO.println("Nickname: " + nickname);9}- A String variable holding the text
Alex. The quotes are not part of the text. - You can build a String from other values with
+. nullmeans "this variable holds nothing at all". It is not the same as empty text"".- Because
Stringis a class, a String variable can do things.length()counts the characters. - Gluing
nullinto text prints the wordnull.
null matters a lot in plugins. Paper returns null when there is nothing to give you, for example player.getKiller() when a player fell to their death, or Bukkit.getPlayerExact("Steve") when Steve is offline. Calling a method on null, such as nickname.length(), crashes with a NullPointerException. You will learn to check for it in Making decisions and Null, exceptions and stack traces. The full toolbox for text is in Working with text.
var: let Java work out the type
Inside a method, you can write var instead of the type, and Java fills in the type from the value. This is called type inference:
1void main() {2 var coins = 50;3 var health = 20.0;4 var name = "Steve";5 var isFlying = true;6 7 coins = coins + 10;8 IO.println(name + " has " + coins + " coins, " + health + " health, flying: " + isFlying);9}- 50 is a whole number, so
coinsbecomes anint. 20.0has a decimal point, sohealthis adouble.- A
String. - A
boolean.
var does not mean "any type". The type is still fixed forever; you just did not type it out. Putting a decimal into coins later fails:
1void main() {2 var coins = 50;3 coins = 12.5;4}coinsis anint, so it cannot hold12.5.
While you are learning, write the real type. int coins = 50; tells you, and anyone reading, exactly what the chest holds. With var player = event.getPlayer(); you have to know what getPlayer() returns to understand the line. You will see var in other people's plugins, and it is fine once types feel natural.
final variables and constants
Put final in front of a variable and it can be assigned only once. Trying to change it is a compile error:
1void main() {2 final int maxHomes = 3;3 IO.println("You can set " + maxHomes + " homes.");4 maxHomes = 5;5}finallocks the chest after the first value goes in.- Not allowed. The compiler stops you, which is exactly the point: some values should never change by accident.
Values that are the same for the whole program, like limits and settings, are called constants. You declare them outside any method with static final, and name them in UPPER_SNAKE_CASE so they stand out:
1static final int MAX_HOMES = 3;2static final int TICKS_PER_SECOND = 20;3static final String SERVER_NAME = "Skyblock Island";4 5void main() {6 IO.println("Welcome to " + SERVER_NAME + "!");7 IO.println("Every player may set " + MAX_HOMES + " homes.");8 IO.println("The server runs " + TICKS_PER_SECOND + " ticks per second.");9}- A constant.
staticmeans it belongs to the program as a whole, not to one method, andfinalmeans it never changes. - A Minecraft server runs 20 ticks per second. Giving the number a name explains what it means wherever it is used.
- Constants can be any type, including String.
- Every method in the file can read the constant.
Plugins use constants the same way, written at the top of a class. You will see this line in the plugin later on this page:
1private static final int MAX_FOOD_LEVEL = 20;- Only this class may use it. Packages, imports and visibility explains
private.
Why bother instead of typing 20? Because a bare 20 in the middle of code could mean 20 food, 20 health, 20 ticks or 20 coins. MAX_FOOD_LEVEL can only mean one thing, and if the value ever changes, you change it in one place. Values that server owners should be able to change go even further, into a config file (see Configuration files).
Converting between types
Between number types: casting
Java converts a smaller number type into a bigger one automatically, because nothing can be lost. Going the other way, from double to int, would throw away the decimals, so Java makes you say so with a cast: the target type in parentheses.
1void main() {2 int foodLevel = 17;3 double asDouble = foodLevel;4 IO.println("int 17 as a double: " + asDouble);5 6 double health = 7.9;7 int wholeHealth = (int) health;8 IO.println("(int) 7.9 = " + wholeHealth);9 10 double damage = -2.7;11 IO.println("(int) -2.7 = " + (int) damage);12 13 double hearts = health / 2;14 int fullHearts = (int) hearts;15 IO.println(health + " health is " + hearts + " hearts, " + fullHearts + " of them full");16}- Automatic: an
intfits in adouble. 17 becomes 17.0. - A cast.
(int)chops off everything after the decimal point, so 7.9 becomes 7, not 8. For proper rounding, useMath.round(see Operators and math). - Chopping works toward zero, so -2.7 becomes -2.
- A heart is 2 health points, so dividing by 2 gives hearts: 3.95.
- Casting to
intkeeps only the whole hearts: 3 full hearts.
If you forget the cast, the compiler refuses with "possible lossy conversion". This is one of the errors you will meet most, because Paper gives you a double for health but many things you calculate from it need to be int:
1void main() {2 double health = 19.5;3 int roundedHealth = health;4}- A
doublecannot go into anintwithout a cast. Fix it with(int) healthif chopping is what you want.
Between text and numbers: parsing
Text that looks like a number is still text. "64" is two characters, not the number 64. To do math with it, parse it into a number. To go the other way, turn a number into text with String.valueOf:
1void main() {2 String typedAmount = "64";3 int amount = Integer.parseInt(typedAmount);4 IO.println("Doubled: " + (amount * 2));5 6 String typedSpeed = "0.5";7 double speed = Double.parseDouble(typedSpeed);8 IO.println("Speed plus one: " + (speed + 1));9 10 int level = 30;11 String levelText = String.valueOf(level);12 IO.println("As text the level has " + levelText.length() + " characters");13 14 IO.println("Text glued: " + typedAmount + typedAmount);15}- Text, as if a player typed it.
- Reads the text and gives back the
int64. - The same for numbers with decimals.
- Turns the number 30 into the text
"30". You will see this all the time in plugins, because many messages and placeholders want text. - Two Strings glued together:
"64" + "64"is"6464", not 128. This is why parsing matters.
If the text is not a number, parsing fails at runtime with a NumberFormatException:
1void main() {2 String typedAmount = "ten";3 int amount = Integer.parseInt(typedAmount);4 IO.println("You asked for " + amount);5}"ten"is not digits, so Java throws an exception and the program stops here.
This is not just theory. When a player types a command like /setfood 15, your plugin receives the 15 as a String, always. You parse it, and you must be ready for players who type /setfood lots. The plugin below shows how.
A plugin full of variables
This plugin adds two commands. /mystats reads your stats into variables of the right types and prints them. /setfood 15 turns the text 15 into a number and sets your hunger bar. Commands get a whole chapter later (Commands, part 1); here, focus on the variables.
Where this file livesjava-variables-types-statssrcmainjavacomexamplejavavariablestypesstatsStatsCommand.java
The package com.example.javavariablestypesstats is the folder path com/example/javavariablestypesstats 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.
- java-variables-types-stats/
- src/main/
- java/com/example/javavariablestypesstats/Package com.example.javavariablestypesstats
- SetFoodCommand.javaCommand (BasicCommand)
- StatsCommand.javayou are hereCommand (BasicCommand)
- VariableStatsPlugin.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/javavariablestypesstats/Package com.example.javavariablestypesstats
- 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.javavariablestypesstats;2 3import io.papermc.paper.command.brigadier.BasicCommand;4import io.papermc.paper.command.brigadier.CommandSourceStack;5import org.bukkit.entity.Player;6 7public final class StatsCommand implements BasicCommand {8 9 private static final int MAX_FOOD_LEVEL = 20;10 11 @Override12 public void execute(CommandSourceStack source, String[] args) {13 if (!(source.getExecutor() instanceof Player player)) {14 source.getSender().sendRichMessage("<red>Only players can use /mystats.");15 return;16 }17 18 String name = player.getName();19 double health = player.getHealth();20 int foodLevel = player.getFoodLevel();21 float saturation = player.getSaturation();22 int level = player.getLevel();23 boolean flying = player.isFlying();24 long firstPlayed = player.getFirstPlayed();25 26 int fullHearts = (int) (health / 2);27 28 player.sendRichMessage("<gold>Stats for " + name);29 player.sendRichMessage("<gray>Health: <red>" + health + " <gray>(" + fullHearts + " full hearts)");30 player.sendRichMessage("<gray>Food: <green>" + foodLevel + "/" + MAX_FOOD_LEVEL);31 player.sendRichMessage("<gray>Saturation: <yellow>" + saturation);32 player.sendRichMessage("<gray>Level: <aqua>" + level);33 player.sendRichMessage("<gray>Flying: <white>" + flying);34 player.sendRichMessage("<gray>First joined: <white>" + firstPlayed + " <gray>ms after 1970");35 }36}- Makes this class a simple command. Paper calls its
executemethod every time someone runs the command. - A constant: the hunger bar is always out of 20.
- The words typed after the command, as Strings.
/mystatsdoes not use them. - Checks that a player ran the command, not the server console, and if so stores that player in the variable
player. Making decisions explains this line fully. - Each of the next lines asks the player for one value and keeps it in a variable whose type matches what the method returns.
- A
double, because health can be 17.5. - Saturation is a
floatin Paper. Usinginthere would not compile. - A yes-or-no value.
- A huge number of milliseconds, way past the
intlimit, so it is along. - Two health points make one heart. The cast keeps only the full hearts, exactly like the
Castingprogram. - Each message glues text and variables together with
+. The tags like<gray>are colors, explained in MiniMessage.
Here is what a player sees after typing /mystats:
Health: 17.5 (8 full hearts)
Food: 18/20
Saturation: 3.2
Level: 12
Flying: false
First joined: 1790431200000 ms after 1970
The second command turns typed text into a number, keeps it in range, and uses it:
Where this file livesjava-variables-types-statssrcmainjavacomexamplejavavariablestypesstatsSetFoodCommand.java
The package com.example.javavariablestypesstats is the folder path com/example/javavariablestypesstats 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.
- java-variables-types-stats/
- src/main/
- java/com/example/javavariablestypesstats/Package com.example.javavariablestypesstats
- SetFoodCommand.javayou are hereCommand (BasicCommand)
- StatsCommand.javaCommand (BasicCommand)
- VariableStatsPlugin.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/javavariablestypesstats/Package com.example.javavariablestypesstats
- 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.javavariablestypesstats;2 3import io.papermc.paper.command.brigadier.BasicCommand;4import io.papermc.paper.command.brigadier.CommandSourceStack;5import org.bukkit.entity.Player;6 7public final class SetFoodCommand implements BasicCommand {8 9 private static final int MAX_FOOD_LEVEL = 20;10 11 @Override12 public void execute(CommandSourceStack source, String[] args) {13 if (!(source.getExecutor() instanceof Player player)) {14 source.getSender().sendRichMessage("<red>Only players can use /setfood.");15 return;16 }17 if (args.length != 1) {18 player.sendRichMessage("<red>Type one number after the command, like /setfood 20.");19 return;20 }21 22 String typed = args[0];23 int amount;24 try {25 amount = Integer.parseInt(typed);26 } catch (NumberFormatException exception) {27 player.sendRichMessage("<red>That is not a whole number. Try /setfood 20.");28 return;29 }30 31 int newFoodLevel = Math.clamp(amount, 0, MAX_FOOD_LEVEL);32 player.setFoodLevel(newFoodLevel);33 player.sendRichMessage("<green>Your food level is now " + newFoodLevel + ".");34 }35 36 @Override37 public String permission() {38 return "variablestats.setfood";39 }40}- Makes sure exactly one word was typed after
/setfood. If not, it explains how to use the command and stops withreturn. - The first word the player typed, as a String. For
/setfood 15this is the text"15".args[0]means "the first item in the list"; lists start counting at 0. - Declared now, assigned on the next lines.
- Turns the text into an
int. - If the text was not a number, Java jumps here instead of crashing. The player gets a friendly message. Exceptions covers
tryandcatch. - Keeps the number between 0 and 20, so
/setfood 500becomes 20 and/setfood -3becomes 0. - Gives the number to Paper.
setFoodLevelwants anint, and that is exactly what we have. - Only players with this permission (operators, by default) can use the command.
Your food level is now 15.
The main class registers both commands when the plugin starts:
Where this file livesjava-variables-types-statssrcmainjavacomexamplejavavariablestypesstatsVariableStatsPlugin.java
The package com.example.javavariablestypesstats is the folder path com/example/javavariablestypesstats 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.
- java-variables-types-stats/
- src/main/
- java/com/example/javavariablestypesstats/Package com.example.javavariablestypesstats
- SetFoodCommand.javaCommand (BasicCommand)
- StatsCommand.javaCommand (BasicCommand)
- VariableStatsPlugin.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/javavariablestypesstats/Package com.example.javavariablestypesstats
- 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.javavariablestypesstats;2 3import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents;4import org.bukkit.plugin.java.JavaPlugin;5 6public final class VariableStatsPlugin extends JavaPlugin {7 8 @Override9 public void onEnable() {10 getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, event -> {11 event.registrar().register("mystats", "Shows your health, food, level and more", new StatsCommand());12 event.registrar().register("setfood", "Sets your food level from 0 to 20", new SetFoodCommand());13 });14 }15}- Paper's moment for adding commands. Copy this pattern for now; Commands, part 1 explains each piece.
- The command name players type, a description, and the object that runs it.
Where this file livesjava-variables-types-statssrcmainresourcesplugin.yml
Files in src/main/resources are copied into the jar exactly as they are. Paper looks for plugin.yml at the top of the jar.
- java-variables-types-stats/
- src/main/
- java/com/example/javavariablestypesstats/Package com.example.javavariablestypesstats
- SetFoodCommand.javaCommand (BasicCommand)
- StatsCommand.javaCommand (BasicCommand)
- VariableStatsPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
- resources/Files copied into the jar as they are
- plugin.ymlyou are hereTells Paper the plugin's name, version and main class
- java/com/example/javavariablestypesstats/Package com.example.javavariablestypesstats
- 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.
1name: VariableStats2version: '1.0.0'3main: com.example.javavariablestypesstats.VariableStatsPlugin4api-version: '26.3'5description: Shows how plugin values fit Java types with /mystats and /setfood.6permissions:7 variablestats.setfood:8 description: Lets a player change their own food level with /setfood.9 default: op- The permission
SetFoodCommandasks for. - Operators get it automatically.
Download this plugin as a Gradle project to run it on your test server.
Common mistakes
Reading a variable before it has a value
1void main() {2 int coins;3 IO.println("Coins: " + coins);4}- Declared, but nothing is ever put in.
- Reading an empty chest is not allowed. Give it a starting value:
int coins = 0;.
Going past the limit: overflow
When an int goes past its maximum, it does not stop or crash. It wraps around to the most negative number. This is called overflow, and it has caused real money bugs on servers:
1void main() {2 int coins = Integer.MAX_VALUE;3 IO.println("Coins: " + coins);4 5 coins = coins + 1;6 IO.println("One more coin: " + coins);7 8 long bigCoins = Integer.MAX_VALUE;9 bigCoins = bigCoins + 1;10 IO.println("With a long: " + bigCoins);11}- The biggest possible
int: 2,147,483,647. - One more coin wraps around to -2,147,483,648. A rich player is suddenly deep in debt.
- A
longhas room to spare, so the same math works.
If a number could ever grow past two billion (total blocks mined on a big server, money that players can farm forever, milliseconds), use long.
Comparing decimals exactly
Computers store decimals in binary, so some values are stored very slightly off. The tiny error shows up when you compare with ==:
1void main() {2 double total = 0.1 + 0.2;3 IO.println("0.1 + 0.2 = " + total);4 IO.println("Is it exactly 0.3? " + (total == 0.3));5 IO.println("Is it close to 0.3? " + (Math.abs(total - 0.3) < 0.0001));6}- Prints 0.30000000000000004, not 0.3.
- So the exact comparison says
false. - Instead, ask "are they really close?": is the difference smaller than a tiny amount?
In plugins this bites when you check health or coordinates, such as player.getHealth() == 20.0 after healing. Prefer comparisons like >=, or check whether the values are close. For money, many plugins store whole cents in a long instead of a double, so the math is always exact.
Writing the wrong kind of value
1void main() {2 float walkSpeed = 0.2;3}- A decimal written plainly is a
double. For afloat, write0.2f.
The same rule gives you the plugin version of this error. Paper's getHealth() returns a double, so this line does not compile:
1int health = player.getHealth();Either use double health = player.getHealth();, or cast if you really want a whole number: int health = (int) player.getHealth();.
Practice
A player stats card
Start from this program and turn it into a stats card for a player named Alex who has 15.5 health, 18 food, level 27, 3 billion coins and is not flying. Then give Alex a quest reward: one level and 500 coins, and print the new values.
1void main() {2 String name = "Alex";3 IO.println("=== " + name + " ===");4}Requirements:
- One variable per value, each with the best type.
- A constant
MAX_FOOD_LEVELfor the 20 in "Food: 18/20". - Calculate the number of full hearts from the health with a cast.
Hint 1
3 billion is more than an int can hold. Which type has room, and which letter do you need at the end of the number?
Hint 2
Full hearts: int fullHearts = (int) (health / 2);. The parentheses make Java divide first, then cast.
Hint 3
The constant goes above void main(): static final int MAX_FOOD_LEVEL = 20;.
Show the solution
Health is a double because of the .5, the coins need a long with an L, and the quest reward changes the variables with level = level + 1 and coins = coins + 500.
1static final int MAX_FOOD_LEVEL = 20;2 3void main() {4 String name = "Alex";5 double health = 15.5;6 int foodLevel = 18;7 int level = 27;8 long coins = 3_000_000_000L;9 boolean isFlying = false;10 11 int fullHearts = (int) (health / 2);12 13 IO.println("=== " + name + " ===");14 IO.println("Health: " + health + " (" + fullHearts + " full hearts)");15 IO.println("Food: " + foodLevel + "/" + MAX_FOOD_LEVEL);16 IO.println("Level: " + level);17 IO.println("Coins: " + coins);18 IO.println("Flying: " + isFlying);19 20 coins = coins + 500;21 level = level + 1;22 IO.println("After the quest: level " + level + ", " + coins + " coins");23}- Without the
L, this line fails with "integer number too large". - 15.5 / 2 is 7.75, and the cast keeps 7.
Show XP progress in /mystats
Add a line to /mystats that shows how far the player is toward their next level, as a whole percentage like "XP to next level: 45%". player.getExp() returns a float from 0 (just leveled up) to 1 (about to level up).
Hint
Multiply the progress by 100 to get a percentage, then cast it to int to drop the decimals.
Show the solution
Add these two lines to StatsCommand, after the other variables and before the messages are sent:
1int xpPercent = (int) (player.getExp() * 100);2player.sendRichMessage("<gray>XP to next level: <aqua>" + xpPercent + "%");With 0.45 progress the player sees "XP to next level: 45%". Without the cast, the line would not compile, because a float times 100 is still a float.
More short challenges are waiting in the Practice Arena.
Recap
- A variable is a named, typed place for one value. Declare it once (
int coins = 50;), then read it by name and change it with=. - Pick the type by the value:
intfor whole numbers,longfor huge ones,doublefor decimals,booleanfor yes or no,Stringfor text. - In plugins, match the type that Paper's method returns:
doublefor health,intfor food and levels,floatfor saturation and yaw. nullmeans "nothing here". Calling a method on it crashes.varlets Java infer the type, but the type is still fixed.finalstops a variable from changing. Constants arestatic finaland named in UPPER_SNAKE_CASE.- Cast with
(int)to drop decimals. Parse text withInteger.parseInt, and turn numbers into text withString.valueOf. Command arguments always arrive as text.
Quick quiz
Which type should hold a player's health?
Health can have decimals, like 17.5, andplayer.getHealth()returns adouble. Anintcould not hold the half point.What does this print?
int coins = 10; coins = coins + 5; IO.println(coins);The right side runs first: 10 + 5 is 15, which is stored back intocoins. The old 10 is replaced.What is the value of
(int) 9.99?Casting tointcuts off the decimals without rounding. It compiles fine because the cast tells Java you accept losing them.A player types
/setfood 15. What is the type ofargs[0]in your command?Everything a player types arrives as text. You turn"15"into a number withInteger.parseInt, and handle the case where it is not a number.Why should a plugin that counts total blocks mined on a big server use
longinstead ofint?Overflow does not crash; it silently wraps to the most negative value. Alonggoes up to about 9.2 quintillion, which is plenty.
Next steps
- Operators and math: calculate damage, chances, distances and ticks.
- Working with text: everything you can do with a String.
- Working with players: many more values you can read and change on a player.