Paper Plugin Guide
File mode0

Java basics

Methods: reusable actions

Package code into named actions with inputs and outputs.

Beginner40 min read

A method is a named action: a block of code with inputs and an output that you can run whenever you need it. On this page you will write your own methods, learn how they take inputs and give back results, and see how a plugin is really a set of methods that Paper calls and methods you write to help them.

You already use methods

Every time you wrote IO.println("..."), you used a method. So are player.sendRichMessage(...), player.getHealth() and Math.max(7, 12). Somebody else wrote the code inside them; you just use them by name. Using a method is called calling it.

Now you will write your own. Methods solve three problems that every plugin has:

  • Repetition. If three commands all need "work out the player's max health", write it once and call it three times.
  • Readability. if (isValidName(nickname)) explains itself. Fifteen lines of character checks do not.
  • Small pieces. A big feature becomes a few small methods you can understand, test and fix one at a time.

Defining and calling a method

Here is a program with a method of its own. Run it:

FirstMethod.javaRuns on Java 25
1void main() {2    announceRestart();3    IO.println("...players keep playing...");4    announceRestart();5}6 7void announceRestart() {8    IO.println("[Server] Restart in 5 minutes!");9    IO.println("[Server] Find a safe place to log out.");10}
  1. A call: the method's name and round brackets. Java jumps into the method, runs all of its lines, then comes back here and carries on.
  2. Normal code between the two calls.
  3. The method's definition. void means it gives nothing back, announceRestart is its name, and the empty () means it needs no inputs.
  4. The method's body: the lines between its braces. They run every time someone calls the method.

The two lines inside announceRestart run twice, because the method is called twice. If you want to change the message, you change it in one place, and both calls get the new version.

Parameters: giving a method inputs

A method that always does exactly the same thing is useful, but most methods need some information first: which player, how much damage, which name. Those inputs are called parameters:

Parameters.javaRuns on Java 25
1void main() {2    greet("Steve", 30);3    greet("Alex", 5);4}5 6void greet(String name, int level) {7    IO.println("Welcome, " + name + "!");8    if (level >= 30) {9        IO.println("Level " + level + ": you can use the enchanting arena.");10    } else {11        IO.println("Level " + level + ": reach level 30 to unlock the arena.");12    }13}
  1. Calls greet with two values: "Steve" and 30. The values you pass in a call are called arguments.
  2. Two parameters, separated by a comma. Each one has a type and a name, like a variable. When Steve's call runs, name is "Steve" and level is 30.
  3. Parameters work like normal variables inside the method, so you can use them in conditions, math and text.

The rule: a call must pass the same number of arguments, of the right types, in the same order as the parameters. Steve goes into name because he comes first. Forget an argument, and Java refuses to compile:

WrongArguments.javaRuns on Java 25
1void main() {2    greet("Steve");3}4 5void greet(String name, int level) {6    IO.println("Welcome, " + name + " (level " + level + ")");7}
  1. Only one argument, but greet needs two.

Read the error from the bottom up: "required: String,int" is what the method wants, "found: String" is what you gave it. You will see this exact error with Paper methods too, for example when you forget an argument to registerEvents.

Returning a value

Many methods work something out and hand back the answer. The answer is the return value. Instead of void, you write the type of the answer before the method's name, and use return to hand it back:

HealAmount.javaRuns on Java 25
1void main() {2    double heal = healAmount(6.0, 20.0, 50);3    IO.println("Healing 6.0 health by 50%: +" + heal);4 5    double newHealth = 17.0 + healAmount(17.0, 20.0, 50);6    IO.println("Healing 17.0 health by 50% gives " + newHealth);7}8 9double healAmount(double health, double maxHealth, double percent) {10    double amount = maxHealth * percent / 100.0;11    double missing = maxHealth - health;12    return Math.min(amount, missing);13}
  1. This method returns a double: the amount of health to add. It needs three inputs to work that out.
  2. 50% of 20 max health is 10.
  3. But a player at 17 health is only missing 3. Healing 10 would go over the maximum.
  4. return hands the result back to whoever called the method, and ends the method right there. The smaller of the two numbers is the safe heal.
  5. The call is replaced by the returned value, which is stored in heal.
  6. A call that returns a value can be used anywhere a value fits, even in the middle of math.
1. Return type what comes back 2. Name what it does 3. Parameters the inputs String formatTime (int totalSeconds) { int minutes = totalSeconds / 60; int seconds = totalSeconds % 60; return String.format("%d:%02d", minutes, seconds); } Body: the steps its variables only exist here 4. return hands the result back Calling it String text = formatTime(125); text is "2:05" The argument 125 goes into the parameter totalSeconds and the body runs. The returned "2:05" takes the place of the call.
The four parts of a method definition, and what happens at a call: the argument goes into the parameter, the body runs, and the returned value takes the place of the call.

Returning is not printing

Beginners often print a result inside a method when they should return it. Printing shows text to a human; returning gives a value to your code. Only a returned value can be used again:

ReturnVsPrint.javaRuns on Java 25
1void main() {2    printTotal(30, 12);3 4    int total = total(30, 12);5    IO.println("Twice the total is " + (total * 2));6}7 8void printTotal(int coins, int bonus) {9    IO.println("Total: " + (coins + bonus));10}11 12int total(int coins, int bonus) {13    return coins + bonus;14}
  1. Prints the total and gives nothing back. The rest of your program cannot use that number.
  2. Returns the total. Now the caller decides what to do with it: print it, double it, save it.
  3. Only possible because total(...) returned a number.

In plugins this matters a lot. A method that returns a value can feed a chat message, an action bar, a scoreboard and a config file. A method that prints to the console can only ever print to the console.

Several returns: deciding inside a method

return ends the method immediately, so you can return from several places. Combined with the guard clauses from Making decisions, this gives very clean decision methods:

HealthLabel.javaRuns on Java 25
1void main() {2    IO.println("2.5 -> " + healthLabel(2.5));3    IO.println("8.0 -> " + healthLabel(8.0));4    IO.println("20.0 -> " + healthLabel(20.0));5}6 7String healthLabel(double health) {8    if (health < 4) {9        return "Critical";10    }11    if (health < 10) {12        return "Hurt";13    }14    return "Healthy";15}
  1. Returns a String: a word that describes the health value.
  2. Below 4, the answer is known, so the method stops here. No else needed.
  3. The last line runs only when no earlier return did. Java checks that every path through the method returns something; leave this line out and you get "missing return statement".

A useful one: formatting time

Cooldowns, countdowns and play time all need to turn a number of seconds into something readable like 2:05. That is a perfect job for a method:

FormatTime.javaRuns on Java 25
1void main() {2    IO.println(formatTime(5));3    IO.println(formatTime(65));4    IO.println(formatTime(600));5    IO.println("Next event in " + formatTime(125) + ".");6}7 8String formatTime(int totalSeconds) {9    int minutes = totalSeconds / 60;10    int seconds = totalSeconds % 60;11    return String.format("%d:%02d", minutes, seconds);12}
  1. One input (a number of seconds), one output (text).
  2. Whole minutes. Integer division drops the remainder: 125 / 60 is 2.
  3. The seconds left over: 125 % 60 is 5.
  4. Builds the text. %02d pads the seconds to two digits, so 5 becomes 05. Working with text explains format codes.
  5. The returned text can be glued into a bigger message.

Methods that call methods

Methods can call other methods, so you can build big ideas from small ones. Minecraft names must be 3 to 16 characters long and use only letters, digits and underscores. Checking that is one method that uses a second, smaller method:

ValidName.javaRuns on Java 25
1void main() {2    String[] names = {"Steve", "xX_Pro_Xx", "Al", "Notch!", "ThisNameIsWayTooLong"};3    for (String name : names) {4        IO.println(name + " -> " + isValidName(name));5    }6}7 8boolean isValidName(String name) {9    if (name.length() < 3 || name.length() > 16) {10        return false;11    }12    for (int i = 0; i < name.length(); i++) {13        if (!isAllowedCharacter(name.charAt(i))) {14            return false;15        }16    }17    return true;18}19 20boolean isAllowedCharacter(char c) {21    return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_';22}
  1. A for-each loop from the last chapter tests five names with one call each.
  2. Returns true or false. Methods that answer a yes-or-no question usually start with is, has or can.
  3. Too short or too long? Then the answer is already known: return false.
  4. The character at position i. The loop checks every character of the name.
  5. A method calling another method. If any character is not allowed, the whole name is invalid, and the method returns at once.
  6. Only reached when the length was fine and every character passed.
  7. A tiny helper with one job. A char is a single character, written in single quotes like 'a'.
  8. You can return a condition directly. Its true or false value is the answer, so no if is needed.

In a plugin: helper methods

Real plugin classes are full of small helper methods like these. Here is a /heal command that heals half of a player's missing health. It uses the exact healAmount method from above, plus a helper that finds the player's maximum health:

HealCommand.javaCompiles on Paper 26.3Compile Lab
Where this file livesjava-methods-demosrcmainjavacomexamplejavamethodsdemoHealCommand.java

The package com.example.javamethodsdemo is the folder path com/example/javamethodsdemo 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-methods-demo/
    • src/main/
      • java/com/example/javamethodsdemo/Package com.example.javamethodsdemo
        • HealCommand.javayou are hereCommand (BasicCommand)
        • MethodsDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • NickCommand.javaCommand (BasicCommand)
        • SessionCommand.javaCommand (BasicCommand)
        • TimeFormat.javaHelper class
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

1package com.example.javamethodsdemo;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.attribute.Attribute;7import org.bukkit.attribute.AttributeInstance;8import org.bukkit.entity.Player;9 10public final class HealCommand implements BasicCommand {11 12    private static final double HEAL_PERCENT = 50;13 14    @Override15    public void execute(CommandSourceStack source, String[] args) {16        if (!(source.getExecutor() instanceof Player player)) {17            source.getSender().sendRichMessage("<red>Only players can be healed.");18            return;19        }20 21        double maxHealth = maxHealthOf(player);22        double amount = healAmount(player.getHealth(), maxHealth, HEAL_PERCENT);23        if (amount <= 0) {24            player.sendRichMessage("<yellow>You are already at full health.");25            return;26        }27 28        player.setHealth(player.getHealth() + amount);29        player.sendRichMessage("<green>Healed <amount> hearts.",30            Placeholder.unparsed("amount", String.valueOf(amount / 2)));31    }32 33    @Override34    public String permission() {35        return "methodsdemo.heal";36    }37 38    private static double maxHealthOf(Player player) {39        AttributeInstance maxHealth = player.getAttribute(Attribute.MAX_HEALTH);40        if (maxHealth == null) {41            return 20.0;42        }43        return maxHealth.getValue();44    }45 46    private static double healAmount(double health, double maxHealth, double percent) {47        double amount = maxHealth * percent / 100.0;48        double missing = maxHealth - health;49        return Math.min(amount, missing);50    }51}
  1. A class is a container for methods (and data). This one holds the command's methods. Classes and objects covers classes properly.
  2. A constant: a named value that never changes. Easy to find and tweak.
  3. Paper calls this method when a player types /heal. You never call it yourself.
  4. Calls our first helper. The long attribute lookup now hides behind a clear name.
  5. Calls our second helper with three arguments: the current health, the maximum and the percent.
  6. At full health there is nothing missing, so the amount is 0.
  7. Paper's method that changes the player's health. Thanks to healAmount, the new value never goes above the maximum.
  8. Health is counted in half hearts, so 10 health is 5 hearts.
  9. Another method Paper calls: it asks which permission is needed to use the command.
  10. A helper that takes a Player as its input. private means only this class can call it; static is explained in a moment.
  11. The player's maximum health setting. Paper may return null for it, so the next lines fall back to the normal 20.
  12. The same method as in the runnable example, copied unchanged. Plain Java logic works the same inside a plugin.
Healed 5.0 hearts.
You are already at full health.

The /nick command uses isValidName and isAllowedCharacter from the runnable example to check a nickname before using it:

NickCommand.javaCompiles on Paper 26.3Compile Lab
Where this file livesjava-methods-demosrcmainjavacomexamplejavamethodsdemoNickCommand.java

The package com.example.javamethodsdemo is the folder path com/example/javamethodsdemo 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-methods-demo/
    • src/main/
      • java/com/example/javamethodsdemo/Package com.example.javamethodsdemo
        • HealCommand.javaCommand (BasicCommand)
        • MethodsDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • NickCommand.javayou are hereCommand (BasicCommand)
        • SessionCommand.javaCommand (BasicCommand)
        • TimeFormat.javaHelper class
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

11@Override12public void execute(CommandSourceStack source, String[] args) {13    if (!(source.getExecutor() instanceof Player player)) {14        source.getSender().sendRichMessage("<red>Only players can have a nickname.");15        return;16    }17    if (args.length != 1) {18        player.sendRichMessage("<red>Usage: /nick NewName");19        return;20    }21 22    String nickname = args[0];23    if (!isValidName(nickname)) {24        player.sendRichMessage("<red>Nicknames need 3 to 16 letters, digits or underscores.");25        return;26    }27 28    player.displayName(Component.text(nickname));29    player.sendRichMessage("<green>Your nickname is now <white><nick></white>.",30        Placeholder.unparsed("nick", nickname));31}
  1. The player must type exactly one word after /nick.
  2. One readable line. All the length and character rules live in the helper method further down the file.
  3. Sets the name shown for this player in chat. It is reset when the player rejoins; saving it is a job for Saving player data.
Nicknames need 3 to 16 letters, digits or underscores.
Your nickname is now Builder_Bob.

Scope: where variables live

A variable exists only inside the block where it was created. This is called its scope. Each method is its own block, so each method has its own private set of variables. Even when two methods use the same variable name, they are two different variables:

Scope.javaRuns on Java 25
1void main() {2    int coins = 100;3 4    addBonus(coins);5    IO.println("After addBonus, coins is still " + coins);6 7    coins = withBonus(coins);8    IO.println("After withBonus, coins is " + coins);9}10 11void addBonus(int coins) {12    coins = coins + 50;13    IO.println("Inside addBonus, its own coins is " + coins);14}15 16int withBonus(int coins) {17    return coins + 50;18}
  1. Passes the value 100 into addBonus, not the variable itself.
  2. This coins is a brand-new variable that belongs to addBonus. It starts as a copy of 100.
  3. Changes only the copy. The copy disappears when the method ends.
  4. Proof: main's coins was never touched.
  5. The fix: return the new value and store it. This is how a method gives a result back to its caller.

The same rule means you cannot reach into another method's variables:

ScopeError.javaRuns on Java 25
1void main() {2    giveReward();3    IO.println("You got " + reward + " coins");4}5 6void giveReward() {7    int reward = 50;8    IO.println("Reward ready: " + reward);9}
  1. Fails: reward only exists inside giveReward. To main, it does not exist at all.

"cannot find symbol" means "I do not know any variable or method with this name here". When you see it, check the spelling first, then check whether the variable was created inside a different block or method. To share a value, return it, or pass it in as an argument. (Classes add a third way, fields, in Classes and objects.)

Overloading: same name, different inputs

Two methods can have the same name if their parameters are different. This is called overloading. Java picks the right one by looking at the arguments in each call:

Overloading.javaRuns on Java 25
1void main() {2    IO.println(formatCoins(1));3    IO.println(formatCoins(250));4    IO.println(formatCoins(3, "emerald"));5}6 7String formatCoins(int amount) {8    return formatCoins(amount, "coin");9}10 11String formatCoins(int amount, String currency) {12    if (amount == 1) {13        return amount + " " + currency;14    }15    return amount + " " + currency + "s";16}
  1. Version 1: just an amount. It calls version 2 with the default currency, so the real logic lives in one place.
  2. Version 2: an amount and a currency name.
  3. Two arguments, so Java picks version 2.

The Paper API uses overloading everywhere. ItemStack.of(Material.DIAMOND) makes one diamond, ItemStack.of(Material.DIAMOND, 16) makes sixteen: same name, different parameters. sendRichMessage has one version with only the text and another that also takes placeholders. In IntelliJ, put the cursor inside the round brackets of a call and press Ctrl+P to list every version and its parameters.

static and instance methods

Look at these two calls:

Two kinds of method
1double health = player.getHealth();2int bigger = Math.max(7, 12);
  1. Called on a specific player. Steve's health and Alex's health are different, so you must say whose health you want. This is an instance method.
  2. Called on the class name, Math. It needs no particular object: the bigger of 7 and 12 is the same for everyone. This is a static method.

An instance method belongs to one particular object (one player, one item, one world) and works with that object's data. A static method belongs to the class itself and only uses its parameters. You write static in front of a method to make it static.

The practice files on this site hide this detail, but most code online uses the classic full form, where main and its helpers are static:

ClassicStatic.javaRuns on Java 25
1public class ClassicStatic {2 3    public static void main(String[] args) {4        System.out.println("Next event in " + formatTime(125));5        System.out.println("Bigger number: " + Math.max(7, 12));6    }7 8    static String formatTime(int totalSeconds) {9        int minutes = totalSeconds / 60;10        int seconds = totalSeconds % 60;11        return String.format("%d:%02d", minutes, seconds);12    }13}
  1. The classic starting point you met in How code reads. It is static, so Java can run it without creating an object first.
  2. A static method can call other static methods in the same class by name.
  3. The same method as before, with static in front, because it only needs its parameter.

In plugins you will see both kinds. The helpers in HealCommand are private static because they only use their parameters. A small class full of static helpers is also common. This plugin keeps formatTime in its own TimeFormat class so every command can use it:

TimeFormat.javaCompiles on Paper 26.3Compile Lab
Where this file livesjava-methods-demosrcmainjavacomexamplejavamethodsdemoTimeFormat.java

The package com.example.javamethodsdemo is the folder path com/example/javamethodsdemo 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-methods-demo/
    • src/main/
      • java/com/example/javamethodsdemo/Package com.example.javamethodsdemo
        • HealCommand.javaCommand (BasicCommand)
        • MethodsDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • NickCommand.javaCommand (BasicCommand)
        • SessionCommand.javaCommand (BasicCommand)
        • TimeFormat.javayou are hereHelper class
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

1package com.example.javamethodsdemo;2 3public final class TimeFormat {4 5    private TimeFormat() {6    }7 8    public static String formatTime(int totalSeconds) {9        int minutes = totalSeconds / 60;10        int seconds = totalSeconds % 60;11        return String.format("%d:%02d", minutes, seconds);12    }13}
  1. Nobody needs a TimeFormat object, so this blocks anyone from making one. You will learn about these "constructors" in Classes and objects.
  2. Same logic as the runnable version. public lets the other classes in the plugin call it.
SessionCommand.javaCompiles on Paper 26.3Compile Lab
Where this file livesjava-methods-demosrcmainjavacomexamplejavamethodsdemoSessionCommand.java

The package com.example.javamethodsdemo is the folder path com/example/javamethodsdemo 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-methods-demo/
    • src/main/
      • java/com/example/javamethodsdemo/Package com.example.javamethodsdemo
        • HealCommand.javaCommand (BasicCommand)
        • MethodsDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • NickCommand.javaCommand (BasicCommand)
        • SessionCommand.javayou are hereCommand (BasicCommand)
        • TimeFormat.javaHelper class
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

10@Override11public void execute(CommandSourceStack source, String[] args) {12    if (!(source.getExecutor() instanceof Player player)) {13        source.getSender().sendRichMessage("<red>The console is always online.");14        return;15    }16 17    long onlineMillis = System.currentTimeMillis() - player.getLastLogin();18    int onlineSeconds = (int) (onlineMillis / 1000);19    player.sendRichMessage("<gray>You have been online for <white><time></white> (minutes:seconds).",20        Placeholder.unparsed("time", TimeFormat.formatTime(onlineSeconds)));21}
  1. The current time in milliseconds. Subtracting the time the player logged in gives how long they have been online.
  2. Milliseconds to seconds, then turned into an int because that is what formatTime takes.
  3. A static method from another class: class name, dot, method name.
You have been online for 12:05 (minutes:seconds).

Do not worry if static still feels fuzzy. Classes and objects explains it fully once you know what an object is. For now: static methods are called on a class name, instance methods are called on an object.

Methods that Paper calls

Here is the big idea that connects this page to every plugin you will write: a plugin has no main method. Paper is the program that runs. Your plugin is a collection of methods that Paper calls at the right moments, plus helper methods that you call yourself. Look at the demo's main class:

MethodsDemoPlugin.javaCompiles on Paper 26.3Compile Lab
Where this file livesjava-methods-demosrcmainjavacomexamplejavamethodsdemoMethodsDemoPlugin.java

The package com.example.javamethodsdemo is the folder path com/example/javamethodsdemo 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-methods-demo/
    • src/main/
      • java/com/example/javamethodsdemo/Package com.example.javamethodsdemo
        • HealCommand.javaCommand (BasicCommand)
        • MethodsDemoPlugin.javayou are hereMain class: Paper starts here (named as main in plugin.yml)
        • NickCommand.javaCommand (BasicCommand)
        • SessionCommand.javaCommand (BasicCommand)
        • TimeFormat.javaHelper class
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

1package com.example.javamethodsdemo;2 3import io.papermc.paper.command.brigadier.Commands;4import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents;5import org.bukkit.plugin.java.JavaPlugin;6 7public final class MethodsDemoPlugin extends JavaPlugin {8 9    @Override10    public void onEnable() {11        getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, event -> {12            Commands commands = event.registrar();13            commands.register("heal", "Heal half of your missing health", new HealCommand());14            commands.register("session", "Show how long you have been online", new SessionCommand());15            commands.register("nick", "Change the name shown in chat", new NickCommand());16        });17        getLogger().info("Commands /heal, /session and /nick are ready.");18    }19 20    @Override21    public void onDisable() {22        getLogger().info("Goodbye!");23    }24}
  1. Says "this method replaces one that Paper already knows about". If you misspell the name, the compiler warns you instead of Paper silently never calling it. Inheritance and interfaces explains the details.
  2. Paper calls this once when the plugin starts. You never call onEnable() yourself.
  3. Hands Paper an object of your command class, so Paper can call its execute method later.
  4. Paper calls this once when the server stops. It is the place to save data.
MethodWho calls itWhen
onEnable()PaperOnce, when your plugin starts
onDisable()PaperOnce, when your plugin stops
@EventHandler methodsPaperEvery time their event happens (see Events and listeners)
execute(...) in a commandPaperEvery time someone runs the command (see Commands, part 1)
Your helpers (healAmount, isValidName)YouWhenever your code calls them
Paper's methods (player.setHealth, sendRichMessage)YouWhenever your code calls them

For the methods Paper calls, the rules are strict: the name and parameters must be exactly what Paper expects (onEnable, execute(CommandSourceStack source, String[] args)), or for event handlers, the one parameter must be an event type. For your own helpers, you choose everything.

  • java-methods-demo/
    • src/
      • main/
        • java/
          • com/example/javamethodsdemo/
            • MethodsDemoPlugin.javaMain class: onEnable registers the commands, onDisable says goodbye
            • HealCommand.java/heal with the maxHealthOf and healAmount helpers
            • SessionCommand.java/session, uses TimeFormat.formatTime
            • NickCommand.java/nick with the isValidName and isAllowedCharacter helpers
            • TimeFormat.javaA small class with one static helper method
        • resources/
          • plugin.ymlName, main class and permissions
Server console
[14:02:11 INFO]: [MethodsDemo] Enabling MethodsDemo v1.0.0[14:02:11 INFO]: [MethodsDemo] Commands /heal, /session and /nick are ready.[14:40:57 INFO]: [MethodsDemo] Disabling MethodsDemo v1.0.0[14:40:57 INFO]: [MethodsDemo] Goodbye!

Download the demo plugin to try the three commands on your test server, or open it in the Compile Lab.

Good names and small methods

The computer does not care what you call a method. People do, including you in three months. These habits make plugin code much easier to read:

  • Name actions with verbs: giveStarterKit, healPlayer, formatTime. Not kit, doStuff or method2.
  • Name yes-or-no questions with is, has or can: isValidName, hasCooldown, canAfford. Then if (canAfford(coins, price)) reads like English.
  • Give each method one job. If you need "and" to describe it ("checks the name and saves it and sends a message"), it probably wants to be two or three methods.
  • Keep methods short. If a method no longer fits on your screen, look for a piece you can move into a helper with a good name.
  • Copied code is a method waiting to happen. If you paste the same five lines into a second place, make a method and call it twice.

Mistakes to avoid

  • Forgetting the brackets. announceRestart; is not a call. A call always has round brackets: announceRestart();.
  • Missing return. A method with a return type must return a value on every path. Java says "missing return statement" when one path has none.
  • Ignoring a returned value. name.toUpperCase(); on its own line does nothing useful: the uppercase text is returned and thrown away. Store it: name = name.toUpperCase(Locale.ROOT);.
  • Arguments in the wrong order. healAmount(maxHealth, health, 50) compiles, because both are double, but gives wrong answers. In IntelliJ, the gray parameter name hints in front of each argument help you spot this.
  • Expecting a method to change your variable. Parameters are copies. Return the new value instead.
  • Writing a method inside another method. Every method sits directly inside the class (or next to main in practice files).
Try it

Titles and prices as methods

In Making decisions you picked a title for a number of kills. Now turn that into a reusable method, so a loop can call it for many players. Finish the two methods in the starter:

  • titleFor(int kills) returns Peaceful (0), Fighter (1 to 9), Warrior (10 to 49), Champion (50 to 99) or Legend (100 or more).
  • canAfford(int coins, int price) returns whether the player has enough coins.
KillTitleMethods.java (starter)Runs on Java 25
1void main() {2    int[] killCounts = {0, 5, 27, 50, 250};3    for (int kills : killCounts) {4        IO.println(kills + " kills: " + titleFor(kills));5    }6 7    IO.println("Can afford a 50 coin kit with 35 coins? " + canAfford(35, 50));8    IO.println("Can afford a 50 coin kit with 80 coins? " + canAfford(80, 50));9}10 11String titleFor(int kills) {12    return "Unknown";13}14 15boolean canAfford(int coins, int price) {16    return false;17}
  1. A placeholder so the starter compiles. Replace it with real logic.
Hint 1

Use early returns in titleFor: if (kills == 0) { return "Peaceful"; }, then the next check, and end with return "Legend";.

Hint 2

canAfford fits in one line: return the comparison itself.

Show the solution

Each check returns as soon as it knows the answer, so no else is needed. canAfford returns the boolean result of the comparison directly.

KillTitleMethodsSolution.javaRuns on Java 25
1void main() {2    int[] killCounts = {0, 5, 27, 50, 250};3    for (int kills : killCounts) {4        IO.println(kills + " kills: " + titleFor(kills));5    }6 7    IO.println("Can afford a 50 coin kit with 35 coins? " + canAfford(35, 50));8    IO.println("Can afford a 50 coin kit with 80 coins? " + canAfford(80, 50));9}10 11String titleFor(int kills) {12    if (kills == 0) {13        return "Peaceful";14    }15    if (kills < 10) {16        return "Fighter";17    }18    if (kills < 50) {19        return "Warrior";20    }21    if (kills < 100) {22        return "Champion";23    }24    return "Legend";25}26 27boolean canAfford(int coins, int price) {28    return coins >= price;29}
  1. Only reached when kills is not 0, so this covers 1 to 9.
  2. The fallback for 100 and more.
  3. The comparison is already true or false, which is exactly the answer.
Try it

One counting method, three items

In Loops, the /diamonds command counted one kind of item. Write a /valuables command that tells players how many diamonds, emeralds and gold ingots they carry, in one message:

75 diamonds, 12 emeralds, 30 gold ingots

Do not copy the counting loop three times. Write it once, as a method.

Hint 1

The method needs two inputs, the player and the item type, and returns a number: private static int countItems(Player player, Material material).

Hint 2

Inside, use the loop from the diamond counter, but compare with material instead of Material.DIAMOND, and finish with return total;.

Hint 3

In execute, call it three times: int diamonds = countItems(player, Material.DIAMOND); and so on.

Show the solution

The material parameter is what makes the method reusable: the same loop counts any item. Adding a fourth item later is one more line in execute.

ValuablesCommand.javaCompiles on Paper 26.3Compile Lab
Where this file livesjava-methods-exercisesrcmainjavacomexamplejavamethodsexerciseValuablesCommand.java

The package com.example.javamethodsexercise is the folder path com/example/javamethodsexercise 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-methods-exercise/
    • src/main/
      • java/com/example/javamethodsexercise/Package com.example.javamethodsexercise
        • MethodsExercisePlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • ValuablesCommand.javayou are hereCommand (BasicCommand)
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

Gray files come with the project template; you rarely edit them. Open the whole project in the Compile Lab, or download it from the project page.

1package com.example.javamethodsexercise;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.Material;7import org.bukkit.entity.Player;8import org.bukkit.inventory.ItemStack;9 10public final class ValuablesCommand implements BasicCommand {11 12    @Override13    public void execute(CommandSourceStack source, String[] args) {14        if (!(source.getExecutor() instanceof Player player)) {15            source.getSender().sendRichMessage("<red>Only players have an inventory to count.");16            return;17        }18 19        int diamonds = countItems(player, Material.DIAMOND);20        int emeralds = countItems(player, Material.EMERALD);21        int gold = countItems(player, Material.GOLD_INGOT);22 23        player.sendRichMessage("<aqua><diamonds> diamonds</aqua>, <green><emeralds> emeralds</green>, <gold><ingots> gold ingots</gold>",24            Placeholder.unparsed("diamonds", String.valueOf(diamonds)),25            Placeholder.unparsed("emeralds", String.valueOf(emeralds)),26            Placeholder.unparsed("ingots", String.valueOf(gold)));27    }28 29    private static int countItems(Player player, Material material) {30        int total = 0;31        for (ItemStack item : player.getInventory().getContents()) {32            if (item != null && item.getType() == material) {33                total += item.getAmount();34            }35        }36        return total;37    }38}
  1. Three calls to one method, each with a different argument.
  2. Two parameters in, one int out.
  3. Counts the slot only if it holds the requested item. != null comes first so empty slots are skipped safely.
  4. Hands the count back to execute.

The finished exercise plugin includes the main class that registers /valuables.

Recap

  • A method is a named block of code. Define it once, call it by name with round brackets as often as you like.
  • Parameters are a method's inputs; the values you pass in a call are arguments. Number, types and order must match.
  • void methods return nothing. Other methods declare a return type and use return to hand back a value, which ends the method.
  • Return values can be reused anywhere; printed text cannot. Prefer returning.
  • Variables only live in the block that created them. Parameters are copies.
  • Overloaded methods share a name but take different parameters, like ItemStack.of(material) and ItemStack.of(material, amount).
  • Static methods are called on a class (Math.max); instance methods on an object (player.getHealth()).
  • A plugin is methods Paper calls (onEnable, event handlers, execute) plus helper methods you call yourself.

Quick quiz

  1. What does void mean in void announceRestart()?

  2. Given int total(int coins, int bonus) { return coins + bonus; }, what is total(30, 12) * 2?

  3. A method void addBonus(int coins) { coins = coins + 50; } is called with a variable holding 100. What does the variable hold afterward?

  4. Why can ItemStack.of(Material.DIAMOND) and ItemStack.of(Material.DIAMOND, 16) both exist?

  5. In a plugin, who calls your onEnable() method?

Next steps