Making decisions: if, else and switch
Run different code depending on what is true right now.
Plugins are full of questions: Is this a player? Do they have permission? Which block did they break? On this page you will learn how Java answers those questions with if, else and switch, and you will use them to build a small plugin with health warnings, rank prefixes, game mode rules and a launch pad.
Why plugins are full of decisions
So far, every program you have written runs every line, top to bottom, every time. Real programs need to choose. A plugin almost never does the same thing for everyone: admins get a red prefix, players without permission get a "no" message, and a pressure plate only launches you if it is the right kind of plate.
Here is a decision you will write in some form in almost every plugin you make:
1if (player.hasPermission("myplugin.fly")) {2 player.setAllowFlight(true);3} else {4 player.sendRichMessage("<red>You are not allowed to fly.");5}- Asks a yes-or-no question: does this player have the
myplugin.flypermission ? - Runs only when the answer is yes.
- Everything in the
elsepart runs only when the answer is no.
Read it out loud and it almost sounds like English: "if the player has permission, let them fly, otherwise tell them no." That is the whole idea of this page. The rest is learning the exact shapes Java accepts, and the mistakes that trip everyone up.
Your first if
An if statement runs a block of code only when a condition is true. Press the green run arrow to try it, then change 4.0 to 15.0 and run it again (in live mode).
1void main() {2 double health = 4.0;3 4 if (health < 6.0) {5 IO.println("Your health is low. Eat something!");6 }7 8 IO.println("Health check finished.");9}- A health value like a player's. In Minecraft, 20.0 is full health (10 hearts), so 4.0 is just 2 hearts.
- The keyword
if, then a condition in round brackets, then an opening curly brace. The conditionhealth < 6.0is either true or false. - This line is inside the braces, so it only runs when the condition is true.
- This line is after the closing brace, outside the if, so it always runs.
The condition must be a boolean: a value that is either true or false. Comparisons such as health < 6.0, coins >= price and level == 30 all produce a boolean, and so do methods like player.hasPermission(...) and player.isFlying(). You met these comparison operators in Operators and math.
The most common if mistake: = instead of ==
One equals sign stores a value (coins = 50 puts 50 into coins). Two equals signs compare values (coins == 50 asks "is coins 50?"). Mix them up and Java refuses to compile:
1void main() {2 int coins = 50;3 4 if (coins = 50) {5 IO.println("Exactly fifty coins!");6 }7}- Wrong: this tries to store 50 instead of asking a question. The result is a number, not true or false, so Java complains.
The error says "int cannot be converted to boolean": Java expected a yes-or-no answer in the brackets and got a number. Whenever you see that message on an if line, look for a missing =.
if and else: two paths
Often you want to do one thing when the condition is true and a different thing when it is false. Add else and a second block:
1void main() {2 int coins = 35;3 int price = 50;4 5 if (coins >= price) {6 coins = coins - price;7 IO.println("You bought a diamond sword!");8 IO.println("Coins left: " + coins);9 } else {10 int missing = price - coins;11 IO.println("You need " + missing + " more coins.");12 }13}- Does the player have enough coins?
>=means "greater than or equal to", so exactly 50 coins is enough. - Only runs when the purchase goes through. A block can hold as many lines as you like.
- Exactly one of the two blocks runs, never both and never neither.
- A variable created inside a block only exists inside that block. You will learn more about this in Methods.
This is the shape of every shop, every permission check and every "are you sure?" in a plugin: one path for yes, one for no.
else if: more than two choices
Health is not just "low" or "fine". You might want four or five levels. Chain extra checks with else if:
1void main() {2 double health = 7.5;3 4 if (health <= 0) {5 IO.println("You are dead.");6 } else if (health < 4) {7 IO.println("CRITICAL: find a bed or a golden apple!");8 } else if (health < 10) {9 IO.println("Hurt: be careful.");10 } else if (health < 20) {11 IO.println("Scratched: you will heal soon.");12 } else {13 IO.println("Full health.");14 }15}- The first question. If it is true, Java runs this block and skips everything else in the chain.
- Only asked when every question above it was false. So when Java gets here, it already knows health is above 0.
- With health 7.5, this is the first true question, so "Hurt" is printed.
- The final
elsehas no condition. It catches everything that did not match above. It is optional.
The important rule: only the first true branch runs. Health 2.0 is less than 4, but it is also less than 10 and less than 20. Java does not care: it stops at the first match. That is why the order of the checks matters so much. Watch what happens when the order is wrong:
1void main() {2 double health = 2.0;3 4 if (health < 10) {5 IO.println("Hurt: be careful.");6 } else if (health < 4) {7 IO.println("CRITICAL: find a bed or a golden apple!");8 } else {9 IO.println("Healthy.");10 }11}- Health 2.0 is less than 10, so this branch wins immediately...
- ...and this "critical" branch can never run, because anything below 4 was already caught by the line above.
In a plugin: a low-health warning
Time to use an else if chain in real plugin code. This listener runs every time something takes damage. If it is a player, it works out how much health they will have left and shows a warning in the action bar (the small text line above the hotbar):
Where this file livesjava-conditions-demosrcmainjavacomexamplejavaconditionsdemoHealthWarningListener.java
The package com.example.javaconditionsdemo is the folder path com/example/javaconditionsdemo 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-conditions-demo/
- src/main/
- java/com/example/javaconditionsdemo/Package com.example.javaconditionsdemo
- ConditionsDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
- GameModeListener.javaListener: reacts to events
- HealthWarningListener.javayou are hereListener: reacts to events
- LaunchPadListener.javaListener: reacts to events
- RankJoinListener.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/javaconditionsdemo/Package com.example.javaconditionsdemo
- 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.javaconditionsdemo;2 3import net.kyori.adventure.text.Component;4import net.kyori.adventure.text.format.NamedTextColor;5import org.bukkit.entity.Player;6import org.bukkit.event.EventHandler;7import org.bukkit.event.Listener;8import org.bukkit.event.entity.EntityDamageEvent;9 10public final class HealthWarningListener implements Listener {11 12 @EventHandler13 public void onDamage(EntityDamageEvent event) {14 if (!(event.getEntity() instanceof Player player)) {15 return;16 }17 double healthAfter = player.getHealth() - event.getFinalDamage();18 if (healthAfter <= 0) {19 return;20 }21 22 if (healthAfter < 4) {23 player.sendActionBar(Component.text("CRITICAL: find a bed or a golden apple!", NamedTextColor.DARK_RED));24 } else if (healthAfter < 10) {25 player.sendActionBar(Component.text("Hurt: be careful.", NamedTextColor.GOLD));26 } else {27 player.sendActionBar(Component.text("Just a scratch.", NamedTextColor.YELLOW));28 }29 }30}- An
event handler : Paper calls this method every time any entity (a player, a cow, a zombie) is about to take damage. See Events and listeners. - Damage can hit any entity, but we only care about players. This line says "if the entity is not a player, stop here". The section on instanceof below explains exactly how it works.
- The player's health now, minus the damage they are about to take. The event fires before the damage is applied, so we do the subtraction ourselves.
- If this hit is going to kill them, a warning is pointless, so the handler stops with
return. - The same ladder as the runnable example: most serious first.
- Shows the text in this player's action bar.
Component.textmakes a piece of colored text; Text and colors covers it.
When a zombie leaves you with 3 health, this appears above your hotbar:
Combining conditions
Real questions often have several parts: "is the player an operator, or do they have the build permission and are they outside spawn?" Java joins conditions with three logical operators:
| Operator | Read it as | True when | Example |
|---|---|---|---|
&& | and | both sides are true | level >= 10 && level < 20 |
|| | or | at least one side is true | isOperator || isVip |
! | not | the value is false | !player.isFlying() |
1void main() {2 boolean isOperator = false;3 boolean hasBuildPermission = true;4 boolean isInSpawn = true;5 int level = 12;6 7 if (isOperator || (hasBuildPermission && !isInSpawn)) {8 IO.println("You may build here.");9 } else {10 IO.println("You cannot build here.");11 }12 13 if (level >= 10 && level < 20) {14 IO.println("You can enter the iron arena.");15 }16 17 if (!isOperator) {18 IO.println("Operator tools are hidden.");19 }20}- Operators may always build. Everyone else needs the permission and must be outside spawn. The round brackets group the "and" part, just like brackets in math.
- A range check: between 10 and 19. You cannot write
10 <= level < 20in Java; you must spell out both halves. - The
!flips true to false and false to true. Read it as "if not operator".
Java is lazy in a useful way. With &&, if the left side is false, Java does not even look at the right side, because the answer is already false. With ||, if the left side is true, it skips the right side. This is called short-circuiting, and plugins rely on it to avoid crashes. You will see it in the launch pad below: block == null || block.getType() != ... only asks for the block's type when there really is a block.
Comparing text: equals, not ==
Text in Java is a String, and Strings must be compared with .equals(...), never with ==. The == operator checks whether two variables point at the very same object in memory, which is usually not what you mean; two Strings with the same letters can be different objects. Working with text explains this in detail. This program reads a rank you type:
1void main() {2 String typed = IO.readln("Type a rank (for example Admin): ");3 String rank = typed.strip();4 5 if (rank.equals("admin")) {6 IO.println("equals: exact match, so Admin with a capital A does not count.");7 }8 9 if (rank.equalsIgnoreCase("admin")) {10 IO.println("equalsIgnoreCase: Welcome, administrator!");11 } else if (rank.equalsIgnoreCase("vip")) {12 IO.println("equalsIgnoreCase: Thanks for supporting the server!");13 } else if (rank.isEmpty()) {14 IO.println("You did not type anything.");15 } else {16 IO.println("Unknown rank: " + rank);17 }18}- Reads a line you type. In file mode the recorded run typed
Admin. - Removes spaces at the start and end, so
" admin "still matches. - An exact match: capital letters count, so
Adminis not equal toadmin. - Ignores capital letters. This is what you usually want for anything a player types.
- True when the String has no characters at all.
In plugins you compare Strings most often when reading what a player typed after a command, for example /shop buy versus /shop sell:
1if (args[0].equalsIgnoreCase("buy")) {2 openBuyMenu(player);3} else if (args[0].equalsIgnoreCase("sell")) {4 openSellMenu(player);5}- The first word the player typed after the command name. Commands, part 1 shows where
argscomes from. - Methods you would write yourself. You will learn to write methods like these in Methods.
In a plugin: rank prefixes on join
This listener picks a prefix for the join message with an else if chain over permissions. Notice the order: Admin is checked first.
Where this file livesjava-conditions-demosrcmainjavacomexamplejavaconditionsdemoRankJoinListener.java
The package com.example.javaconditionsdemo is the folder path com/example/javaconditionsdemo 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-conditions-demo/
- src/main/
- java/com/example/javaconditionsdemo/Package com.example.javaconditionsdemo
- ConditionsDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
- GameModeListener.javaListener: reacts to events
- HealthWarningListener.javaListener: reacts to events
- LaunchPadListener.javaListener: reacts to events
- RankJoinListener.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/javaconditionsdemo/Package com.example.javaconditionsdemo
- 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.
14@EventHandler15public void onJoin(PlayerJoinEvent event) {16 Player player = event.getPlayer();17 18 String prefix;19 if (player.hasPermission("conditionsdemo.rank.admin")) {20 prefix = "<red>[Admin]</red>";21 } else if (player.hasPermission("conditionsdemo.rank.vip")) {22 prefix = "<gold>[VIP]</gold>";23 } else {24 prefix = "<gray>[Member]</gray>";25 }26 event.joinMessage(MINI_MESSAGE.deserialize(prefix + " <white><name></white> <gray>joined the game",27 Placeholder.unparsed("name", player.getName())));28 29 String greeting = player.hasPlayedBefore() ? "Welcome back" : "Welcome for the first time";30 player.sendRichMessage("<green><greeting>, <name>!",31 Placeholder.unparsed("greeting", greeting),32 Placeholder.unparsed("name", player.getName()));33}- Declares the variable without a value yet. Every branch below gives it one, and Java checks that no path leaves it empty.
- Server operators have this permission by default (see
plugin.ymlbelow), so they get the Admin prefix. - Only checked when the player is not an admin. A server owner grants it with a permissions plugin such as LuckPerms.
- The fallback for everyone else.
- Replaces the yellow "joined the game" line that everyone sees. The prefix is glued to the front with
+. - A one-line choice using the ternary operator, explained further down.
With an operator called Steve and a regular player called Alex, chat looks like this:
[Member] Alex joined the game
Welcome back, Alex!
Nested ifs vs guard clauses
An if block can contain another if. That is called nesting, and it is fine in small doses. But plugin code often has to check three or four things before doing any real work, and nesting each check inside the last one creates a staircase of braces that is hard to read. Compare these two programs. They do exactly the same thing:
1void main() {2 boolean isPlayer = true;3 boolean hasPermission = true;4 int homesSet = 3;5 int maxHomes = 3;6 7 if (isPlayer) {8 if (hasPermission) {9 if (homesSet < maxHomes) {10 IO.println("Home saved!");11 } else {12 IO.println("You already have " + maxHomes + " homes.");13 }14 } else {15 IO.println("You do not have permission.");16 }17 } else {18 IO.println("Only players can set homes.");19 }20}- Level one.
- Level two, inside level one.
- Level three. The real work, saving the home, is buried in the middle.
- This message belongs to the very first
if, but it sits at the bottom, far away from it.
1void main() {2 boolean isPlayer = true;3 boolean hasPermission = true;4 int homesSet = 3;5 int maxHomes = 3;6 7 if (!isPlayer) {8 IO.println("Only players can set homes.");9 return;10 }11 if (!hasPermission) {12 IO.println("You do not have permission.");13 return;14 }15 if (homesSet >= maxHomes) {16 IO.println("You already have " + maxHomes + " homes.");17 return;18 }19 20 IO.println("Home saved!");21}- Check for a problem. If there is one, say so...
- ...and stop.
returnleaves the method right away, so nothing below it runs. - Each check is flipped: we test for the bad case, so the good case falls through.
- The real work sits at the bottom, with no nesting at all. If Java gets here, every check passed.
The second style uses guard clauses: short ifs at the start that return early when there is nothing to do. Plugin developers use them everywhere, because event handlers are full of "this is not my business" checks.
In a plugin: a launch pad
This listener turns light weighted pressure plates (the gold ones) into launch pads. It has three "not my business" checks before it does anything, so it is written with guard clauses:
Where this file livesjava-conditions-demosrcmainjavacomexamplejavaconditionsdemoLaunchPadListener.java
The package com.example.javaconditionsdemo is the folder path com/example/javaconditionsdemo 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-conditions-demo/
- src/main/
- java/com/example/javaconditionsdemo/Package com.example.javaconditionsdemo
- ConditionsDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
- GameModeListener.javaListener: reacts to events
- HealthWarningListener.javaListener: reacts to events
- LaunchPadListener.javayou are hereListener: reacts to events
- RankJoinListener.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/javaconditionsdemo/Package com.example.javaconditionsdemo
- 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.
16@EventHandler17public void onStep(PlayerInteractEvent event) {18 if (event.getAction() != Action.PHYSICAL) {19 return;20 }21 Block block = event.getClickedBlock();22 if (block == null || block.getType() != Material.LIGHT_WEIGHTED_PRESSURE_PLATE) {23 return;24 }25 Player player = event.getPlayer();26 if (!player.hasPermission("conditionsdemo.launchpad")) {27 return;28 }29 30 Vector launch = player.getLocation().getDirection().multiply(1.5).setY(1.0);31 player.setVelocity(launch);32 player.sendActionBar(Component.text("Whoosh!", NamedTextColor.AQUA));33}PlayerInteractEventfires for left clicks, right clicks and for stepping on things like pressure plates.- Guard 1: we only care about stepping on something (a "physical" action), not clicking.
!=means "is not equal to". - The block involved. It can be
null(nothing), for example when a player clicks the air. - Guard 2, with short-circuiting: if
blockisnull, Java stops here and never callsgetType()on nothing, which would crash. - Guard 3: players without the permission walk over the plate normally.
- The direction the player is looking, made 1.5 times stronger, with an upward push of 1.0. A
Vectoris a direction plus a strength. - Throws the player in that direction.
switch: choosing by value
When you compare one value against a list of exact options ("is the mode survival, creative, adventure or spectator?"), an else if chain gets repetitive. A switch does the same job more neatly. You will see two styles online. First the classic switch statement:
1void main() {2 String mode = "creative";3 4 switch (mode) {5 case "survival":6 IO.println("Survive, mine and craft.");7 break;8 case "creative":9 IO.println("Unlimited blocks and flying.");10 break;11 case "adventure":12 IO.println("Look, but do not break.");13 break;14 default:15 IO.println("Unknown game mode: " + mode);16 }17}- Look at the value of
modeand jump to the matchingcase. - A label: the code after it runs when
modeequals"creative". Switch compares Strings with equals for you, so this is safe. - Leaves the switch. Forget it, and Java keeps running into the next case.
- Runs when no case matched, like the final
else.
That break is a famous trap. Without it, Java "falls through" into the next case and runs that code too. This is called fall-through:
1void main() {2 String mode = "survival";3 4 switch (mode) {5 case "survival":6 IO.println("Survive, mine and craft.");7 case "creative":8 IO.println("Unlimited blocks and flying.");9 break;10 default:11 IO.println("Unknown game mode: " + mode);12 }13}- No
breakafter this line... - ...so Java keeps going and prints the creative text too, even though the mode is survival.
Modern switch expressions
Modern Java (and modern Paper code) mostly uses the arrow style instead. It has no fall-through, needs no break, and can hand back a value directly. That makes it a switch expression:
1void main() {2 String mode = "spectator";3 4 String description = switch (mode) {5 case "survival" -> "Survive, mine and craft.";6 case "creative" -> "Unlimited blocks and flying.";7 case "adventure" -> "Look, but do not break.";8 case "spectator" -> "Fly through walls and watch.";9 default -> "Unknown game mode: " + mode;10 };11 IO.println(description);12 13 int lives = switch (mode) {14 case "hardcore" -> 1;15 case "survival", "adventure" -> 3;16 default -> {17 IO.println("Nobody can die in " + mode + " mode.");18 yield 0;19 }20 };21 IO.println("Lives: " + lives);22}- The whole switch produces a value, which goes straight into
description. - Arrow style: the value after
->is the result for this case. Only one case ever runs, so there is nothing to fall through. - A switch expression must cover every possible value. For Strings that means you need a
default. - A switch expression is part of a statement, so it ends with a semicolon after the closing brace.
- Several values can share one case, separated by commas.
- When a case needs more than one line, give it a block in braces...
- ...and use
yieldto hand back the result from inside the block.
In a plugin: switch on a game mode
Paper describes many fixed lists of things with an enum: a type that has a fixed set of named values. GameMode is one: its only values are SURVIVAL, CREATIVE, ADVENTURE and SPECTATOR. Switching on an enum is one of the most common uses of switch in plugins. (Enums get their own chapter: Enums, records and modern Java.)
Where this file livesjava-conditions-demosrcmainjavacomexamplejavaconditionsdemoGameModeListener.java
The package com.example.javaconditionsdemo is the folder path com/example/javaconditionsdemo 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-conditions-demo/
- src/main/
- java/com/example/javaconditionsdemo/Package com.example.javaconditionsdemo
- ConditionsDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
- GameModeListener.javayou are hereListener: reacts to events
- HealthWarningListener.javaListener: reacts to events
- LaunchPadListener.javaListener: reacts to events
- RankJoinListener.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/javaconditionsdemo/Package com.example.javaconditionsdemo
- 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@EventHandler14public void onGameModeChange(PlayerGameModeChangeEvent event) {15 Player player = event.getPlayer();16 GameMode newMode = event.getNewGameMode();17 boolean inNether = player.getWorld().getEnvironment() == World.Environment.NETHER;18 19 if (newMode == GameMode.CREATIVE && inNether && !player.hasPermission("conditionsdemo.creative.nether")) {20 event.setCancelled(true);21 player.sendRichMessage("<red>Creative mode is not allowed in the Nether.");22 return;23 }24 25 String description = switch (newMode) {26 case SURVIVAL -> "Survive, mine and craft.";27 case CREATIVE -> "Unlimited blocks and flying.";28 case ADVENTURE -> "Look, but do not break.";29 case SPECTATOR -> "Fly through walls and watch.";30 };31 player.sendRichMessage("<gray>Game mode changed. <yellow><description>",32 Placeholder.unparsed("description", description));33}- Fires just before a player's game mode changes, for example after
/gamemode creative. It can be canceled. - Saving a condition in a well-named boolean variable makes the if below read like a sentence.
- Three conditions joined with
&&: switching to creative, while in the Nether, without the special permission. All three must be true. Enum values are compared with==, which is correct for enums. - Stops the change. The player stays in their old game mode.
- Inside the switch you write just
SURVIVAL, notGameMode.SURVIVAL; Java knows which enum it is. - All four game modes are covered, so no
defaultis needed. If Minecraft ever added a fifth mode, the compiler would point at this switch and tell you to handle it.
Creative mode is not allowed in the Nether.
The ternary operator: small choices
Sometimes you only need to choose between two values, like "coin" or "coins". A full if and else for that feels heavy. The ternary operator does it in one line: condition ? valueIfTrue : valueIfFalse.
1void main() {2 int ping = 145;3 4 String color;5 if (ping < 80) {6 color = "green";7 } else if (ping < 200) {8 color = "yellow";9 } else {10 color = "red";11 }12 IO.println("Ping " + ping + " ms is shown in " + color);13 14 String quality = ping < 100 ? "good" : "laggy";15 IO.println("Your connection is " + quality + ".");16 17 int coins = 1;18 IO.println("You have " + coins + (coins == 1 ? " coin" : " coins"));19}- A three-way choice is clearest as an else if chain. Ping is how long, in milliseconds, a message takes to reach the player.
- Read it as: "is ping below 100? If so,
"good", otherwise"laggy"". - A classic use: picking the singular or plural word. The brackets around the ternary keep it separate from the
+signs.
In plugins, the ternary operator shows up for small things like messages. The join listener above uses it to greet new and returning players differently:
Where this file livesjava-conditions-demosrcmainjavacomexamplejavaconditionsdemoRankJoinListener.java
The package com.example.javaconditionsdemo is the folder path com/example/javaconditionsdemo 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-conditions-demo/
- src/main/
- java/com/example/javaconditionsdemo/Package com.example.javaconditionsdemo
- ConditionsDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
- GameModeListener.javaListener: reacts to events
- HealthWarningListener.javaListener: reacts to events
- LaunchPadListener.javaListener: reacts to events
- RankJoinListener.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/javaconditionsdemo/Package com.example.javaconditionsdemo
- 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.
29String greeting = player.hasPlayedBefore() ? "Welcome back" : "Welcome for the first time";- True when the server already has saved data for this player.
Another one you might write for a /fly status message:
1String state = player.isFlying() ? "<green>on" : "<red>off";2player.sendRichMessage("<gray>Flight: " + state);Pattern matching with instanceof (a preview)
In plugins you constantly need to ask "what kind of thing is this?" Who ran this command: a player, or the server console? What took damage: a player, or a zombie? The instanceof keyword answers that question, and modern Java lets you name the result in the same step. This is called pattern matching:
1if (sender instanceof Player player) {2 player.setAllowFlight(true);3} else {4 sender.sendRichMessage("<red>Only players can fly.");5}- Two jobs in one: checks whether
senderis a player, and if so, gives you a variable calledplayerof typePlayerto use inside the block. - Only players can fly, so this method only exists on
Player. That is why we needed theplayervariable.
The health warning listener used the guard clause version of the same idea: if (!(event.getEntity() instanceof Player player)) { return; }. Read it as "if the entity is not a player, stop". The extra round brackets are needed so that ! flips the whole instanceof check. After that line, Java knows the entity is a player, and player can be used for the rest of the method. You will understand fully why this works in Inheritance and interfaces; for now, learn it as a pattern.
The whole plugin
All four listeners live in one small plugin. The main class registers them:
Where this file livesjava-conditions-demosrcmainjavacomexamplejavaconditionsdemoConditionsDemoPlugin.java
The package com.example.javaconditionsdemo is the folder path com/example/javaconditionsdemo 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-conditions-demo/
- src/main/
- java/com/example/javaconditionsdemo/Package com.example.javaconditionsdemo
- ConditionsDemoPlugin.javayou are hereMain class: Paper starts here (named as main in plugin.yml)
- GameModeListener.javaListener: reacts to events
- HealthWarningListener.javaListener: reacts to events
- LaunchPadListener.javaListener: reacts to events
- RankJoinListener.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/javaconditionsdemo/Package com.example.javaconditionsdemo
- 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.javaconditionsdemo;2 3import org.bukkit.plugin.PluginManager;4import org.bukkit.plugin.java.JavaPlugin;5 6public final class ConditionsDemoPlugin extends JavaPlugin {7 8 @Override9 public void onEnable() {10 PluginManager pluginManager = getServer().getPluginManager();11 pluginManager.registerEvents(new HealthWarningListener(), this);12 pluginManager.registerEvents(new RankJoinListener(), this);13 pluginManager.registerEvents(new GameModeListener(), this);14 pluginManager.registerEvents(new LaunchPadListener(), this);15 getLogger().info("Health warnings, rank greetings, game mode rules and launch pads are ready.");16 }17}- Paper calls this when the plugin starts. It is the place to register listeners.
- One line per listener. Forget one, and that feature silently does nothing.
And plugin.yml declares the permissions the code checks, with sensible defaults:
Where this file livesjava-conditions-demosrcmainresourcesplugin.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-conditions-demo/
- src/main/
- java/com/example/javaconditionsdemo/Package com.example.javaconditionsdemo
- ConditionsDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
- GameModeListener.javaListener: reacts to events
- HealthWarningListener.javaListener: reacts to events
- LaunchPadListener.javaListener: reacts to events
- RankJoinListener.javaListener: reacts to events
- 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/javaconditionsdemo/Package com.example.javaconditionsdemo
- 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: ConditionsDemo2version: '1.0.0'3main: com.example.javaconditionsdemo.ConditionsDemoPlugin4api-version: '26.3'5description: Health warnings, rank greetings, game mode rules and launch pads, all built from if, else and switch.6permissions:7 conditionsdemo.rank.admin:8 description: Shows the Admin prefix when joining.9 default: op10 conditionsdemo.rank.vip:11 description: Shows the VIP prefix when joining.12 default: false13 conditionsdemo.creative.nether:14 description: Allows creative mode in the Nether.15 default: false16 conditionsdemo.launchpad:17 description: Lets a player use launch pads.18 default: true- Operators have this permission automatically.
- Nobody has it until a server owner grants it, not even operators.
- Everyone has it unless a server owner takes it away.
- java-conditions-demo/
- src/
- main/
- java/
- com/example/javaconditionsdemo/
- ConditionsDemoPlugin.javaMain class: registers the four listeners
- HealthWarningListener.javaelse if chain: action bar warnings when hurt
- RankJoinListener.javaPermission checks for prefixes, ternary greeting
- GameModeListener.javaCombined conditions and a switch over GameMode
- LaunchPadListener.javaGuard clauses: gold pressure plates launch players
- com/example/javaconditionsdemo/
- resources/
- plugin.ymlName, main class and the four permissions
- java/
- main/
- src/
[14:02:11 INFO]: [ConditionsDemo] Enabling ConditionsDemo v1.0.0[14:02:11 INFO]: [ConditionsDemo] Health warnings, rank greetings, game mode rules and launch pads are ready.Download the whole plugin as a Gradle project, run it with Run Paper Server, and try each feature: take fall damage, rejoin, place a gold pressure plate and run across it, then go to the Nether and try /gamemode creative. You can also open it in the Compile Lab.
Mistakes to avoid
- Using
=instead of==. Java stops you with "cannot be converted to boolean". - Comparing Strings with
==. UseequalsorequalsIgnoreCase. The==version often seems to work in small tests and then fails with real player input. - Checking in the wrong order. In an else if chain, the first true branch wins. Put the most specific check first.
- Forgetting
breakin a classic switch. Or avoid the problem completely with the arrow style. - Leaving out the braces. Without braces, only the very next line belongs to the if. The second line below always runs, even though the indentation suggests otherwise:
1if (health < 4)2 IO.println("Critical!");3 IO.println("Drink a potion!");- A semicolon right after the condition.
if (health < 4);ends the if right there, with an empty body, so the block below always runs. Java does not warn you. Always write{straight after the closing bracket.
Titles for kills
Many PvP servers give players a title based on how many players they have defeated. Finish this program so it picks the right title for any number of kills:
| Kills | Title |
|---|---|
| 0 | Peaceful |
| 1 to 9 | Fighter |
| 10 to 49 | Warrior |
| 50 to 99 | Champion |
| 100 or more | Legend |
1void main() {2 int kills = 27;3 String title = "Unknown";4 5 IO.println("Kills: " + kills);6 IO.println("Title: " + title);7}Test it with 0, 5, 27, 50, 99 and 250 kills.
Hint 1
Use an else if chain and set title in every branch. Start with the special case, 0.
Hint 2
Because Java stops at the first true branch, you do not need kills >= 10 && kills < 50. By the time Java reaches kills < 50, it already knows kills is at least 10.
Show the solution
The chain goes from the smallest number up, so each branch only has to check its upper limit. A negative number of kills should never happen, but checking for it costs one line and makes bad data obvious.
1void main() {2 int kills = 27;3 String title;4 5 if (kills < 0) {6 title = "Invalid";7 } else if (kills == 0) {8 title = "Peaceful";9 } else if (kills < 10) {10 title = "Fighter";11 } else if (kills < 50) {12 title = "Warrior";13 } else if (kills < 100) {14 title = "Champion";15 } else {16 title = "Legend";17 }18 19 IO.println("Kills: " + kills);20 IO.println("Title: " + title);21}- No starting value: every branch sets one, and the compiler checks that.
- Only reached when kills is above 0, so this means 1 to 9.
- 100 or more.
Put the titles into a plugin
Now make it real. Write a listener that, when a player joins, tells them how many players they have defeated and what their title is, with a different color for each title. Players can see this:
Hint 1
Start from the PlayerJoinEvent listener in Events and listeners.
Hint 2
Minecraft already counts player kills as a statistic: int kills = player.getStatistic(Statistic.PLAYER_KILLS);
Hint 3
Put the color tag into the title text, for example title = "<gold>Warrior";, then add it to the end of the message with +.
Show the solution
The else if chain is the same as in the Java program. Only the source of the number changed: instead of a fixed value, the player's real statistic. This is how most plugin logic is born: work it out in plain Java first, then plug in values from Paper.
Where this file livesjava-conditions-exercisesrcmainjavacomexamplejavaconditionsexerciseKillTitleListener.java
The package com.example.javaconditionsexercise is the folder path com/example/javaconditionsexercise 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-conditions-exercise/
- src/main/
- java/com/example/javaconditionsexercise/Package com.example.javaconditionsexercise
- ConditionsExercisePlugin.javaMain class: Paper starts here (named as main in plugin.yml)
- KillTitleListener.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/javaconditionsexercise/Package com.example.javaconditionsexercise
- 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.javaconditionsexercise;2 3import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;4import org.bukkit.Statistic;5import org.bukkit.entity.Player;6import org.bukkit.event.EventHandler;7import org.bukkit.event.Listener;8import org.bukkit.event.player.PlayerJoinEvent;9 10public final class KillTitleListener implements Listener {11 12 @EventHandler13 public void onJoin(PlayerJoinEvent event) {14 Player player = event.getPlayer();15 int kills = player.getStatistic(Statistic.PLAYER_KILLS);16 17 String title;18 if (kills == 0) {19 title = "<green>Peaceful";20 } else if (kills < 10) {21 title = "<yellow>Fighter";22 } else if (kills < 50) {23 title = "<gold>Warrior";24 } else if (kills < 100) {25 title = "<red>Champion";26 } else {27 title = "<dark_purple>Legend";28 }29 30 player.sendRichMessage("<gray>Player kills: <white><kills></white>. Your title: " + title,31 Placeholder.unparsed("kills", String.valueOf(kills)));32 }33}- The same statistic you can see in the game's Statistics screen, saved by the server for every player.
- Each title carries its own MiniMessage color tag.
- The title, with its color, is glued onto the end of the message.
- Placeholders take text, so the number is turned into a String first.
Remember to register the listener in your main class's onEnable. The finished exercise plugin is ready to download.
Recap
if (condition) { ... }runs a block only when the condition istrue. Addelsefor the other path.- In an
else ifchain only the first true branch runs, so put the most specific check first. - Join conditions with
&&(and),||(or) and!(not). Short-circuiting makesx == null || ...safe. - Compare Strings with
equalsorequalsIgnoreCase; compare numbers and enum values with==. - Guard clauses check for problems first and
returnearly. Event handlers are full of them. - Arrow-style switch expressions pick a value by matching cases, with no fall-through. Switches on enums like
GameModecan cover every value without adefault. condition ? a : bchooses between two values in one line.if (sender instanceof Player player)checks the type and gives you a ready-to-use variable.
Quick quiz
Health is
3. The chain checkshealth < 10first, thenelse if (health < 4). What happens?Java stops at the first true condition in an else if chain. Java does not look for the "best" match; that is why the most specific check has to come first.A player types
/shop BUY. Which check matches it?equalsIgnoreCaseignores capital letters, soBUY,Buyandbuyall match.equalsneeds the exact same capitals, and==should never be used to compare Strings.Why is
if (block == null || block.getType() != Material.STONE)safe whenblockisnull?This is short-circuiting. If the left side of||is true, the whole condition is already true, so Java skips the right side. Calling any method onnullwould crash with a NullPointerException.What is the main reason plugin developers use guard clauses in event handlers?
Guard clauses are about readability. Each check sits next to its message and the main work is not buried inside several levels of braces. Paper does not care which style you use.Why does this switch expression compile without a
default?String name = switch (mode) { case SURVIVAL -> "S"; case CREATIVE -> "C"; case ADVENTURE -> "A"; case SPECTATOR -> "Sp"; };(modeis aGameMode)A switch expression must produce a value for every possible input. An enum has a fixed list of values, so covering all of them is enough. A String switch can never cover every possible String, so it needs adefault.
Next steps
- Loops: do something for every online player, every slot or every second of a countdown.
- Methods: put your decisions into named, reusable pieces like
titleFor(kills). - Practice Arena: plugin challenges that need exactly these checks, with real tests.