Paper Plugin Guide
File mode0

Paper essentials

Inventories

Player inventories, armor, chests and moving items around safely.

Beginner60 min read

Players carry items, wear armor, store things in chests and click around in windows, and plugins need to see and change all of it. On this page you will learn which slot is which, how to read, count, add and remove items safely, and how to react when players click inside an inventory. At the end you build a /sell command and a /trash bin.

What is an inventory?

An inventory is a numbered row of slots, and each slot holds either nothing or one ItemStack. In Java it is an object of the type Inventory. Many things in Minecraft have one:

  • A player (the thing you open with E), through player.getInventory().
  • A player's ender chest, through player.getEnderChest().
  • A container block such as a chest, barrel, hopper or furnace.
  • A menu a plugin builds just for show. The page Building GUI menus is all about those.

Every Inventory offers the same methods, whatever it belongs to: getItem(slot), setItem(slot, item), addItem(item), contains(...) and so on. Learn them once and they work on all of these.

The player inventory: which slot is which

A player inventory has 41 slots you can see, and they are numbered in a fixed order:

  • 0 to 8: the hotbar, left to right.
  • 9 to 35: the main inventory, starting top left and reading like a book, row by row.
  • 36 to 39: the armor slots: 36 boots, 37 leggings, 38 chestplate, 39 helmet.
  • 40: the off hand.
Player inventory slot numbers In a PlayerInventory the hotbar is slots 0 to 8, the main inventory is 9 to 35, armor is 36 boots, 37 leggings, 38 chestplate and 39 helmet, and the off hand is 40. Armor 39 helmet 38 chestplate 37 leggings 36 boots 40 off hand Main inventory: slots 9 to 35 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 Hotbar: slots 0 to 8 0 1 2 3 4 5 6 7 8 The hotbar is the bottom row you see on screen while playing. player.getInventory().getItem(0) is the first hotbar slot. Armor slots 36 to 39 also have getHelmet(), getBoots() and friends.
The slot numbers of a player inventory.

Here is a player inventory with a few items placed, labeled with the slot each one occupies (hover an item to read its label):

0: diamond_sword, 8: bread, 9: oak_log, 35: cobblestone, 36: iron_boots, 39: iron_helmet, 40: shield

Reading and writing one slot

HandAndSlots.javaCompiles on Paper 26.3Compile Lab
Where this file livesinventories-snippetssrcmainjavacomexampleinventoriessnippetsHandAndSlots.java

The package com.example.inventoriessnippets is the folder path com/example/inventoriessnippets 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.

  • inventories-snippets/
    • src/main/java/com/example/inventoriessnippets/Package com.example.inventoriessnippets
      • ArmorAndEquipment.javaHelper class
      • Containers.javaHelper class
      • CountingAndRemoving.javaHelper class
      • HandAndSlots.javayou are hereHelper class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

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 describeSlot(Player player, int slot) {30    ItemStack item = player.getInventory().getItem(slot);31    if (item == null || item.isEmpty()) {32        return "slot " + slot + " is empty";33    }34    return "slot " + slot + " holds " + item.getAmount() + " x " + item.getType();35}
  1. Looks into one slot. The result is the ItemStack stored there.
  2. An empty slot can come back as null (Java's word for "no object at all") or as an empty stack, so always check for both. Checking only one is a classic source of crashes.
  3. Only reached when there is a real item, so it is safe to ask for its amount and type.
HandAndSlots.javaCompiles on Paper 26.3Compile Lab
Where this file livesinventories-snippetssrcmainjavacomexampleinventoriessnippetsHandAndSlots.java

The package com.example.inventoriessnippets is the folder path com/example/inventoriessnippets 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.

  • inventories-snippets/
    • src/main/java/com/example/inventoriessnippets/Package com.example.inventoriessnippets
      • ArmorAndEquipment.javaHelper class
      • Containers.javaHelper class
      • CountingAndRemoving.javaHelper class
      • HandAndSlots.javayou are hereHelper class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

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.

15void putDiamondInFirstHotbarSlot(Player player) {16    player.getInventory().setItem(0, ItemStack.of(Material.DIAMOND));17}
  1. Puts the item in slot 0 and replaces whatever was there. Nothing is merged and nothing comes back, the old item is simply gone.

The item in the hand

The most common read in plugin code is "what is the player holding?":

HandAndSlots.javaCompiles on Paper 26.3Compile Lab
Where this file livesinventories-snippetssrcmainjavacomexampleinventoriessnippetsHandAndSlots.java

The package com.example.inventoriessnippets is the folder path com/example/inventoriessnippets 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.

  • inventories-snippets/
    • src/main/java/com/example/inventoriessnippets/Package com.example.inventoriessnippets
      • ArmorAndEquipment.javaHelper class
      • Containers.javaHelper class
      • CountingAndRemoving.javaHelper class
      • HandAndSlots.javayou are hereHelper class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

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.

10boolean isHoldingSomething(Player player) {11    ItemStack inHand = player.getInventory().getItemInMainHand();12    return !inHand.isEmpty();13}
  1. The stack in the selected hotbar slot. With an empty hand you get an empty stack (air), never null.
  2. An empty hand is an empty stack, so isEmpty() is the right question, not "is it null?".
HandAndSlots.javaCompiles on Paper 26.3Compile Lab
Where this file livesinventories-snippetssrcmainjavacomexampleinventoriessnippetsHandAndSlots.java

The package com.example.inventoriessnippets is the folder path com/example/inventoriessnippets 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.

  • inventories-snippets/
    • src/main/java/com/example/inventoriessnippets/Package com.example.inventoriessnippets
      • ArmorAndEquipment.javaHelper class
      • Containers.javaHelper class
      • CountingAndRemoving.javaHelper class
      • HandAndSlots.javayou are hereHelper class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

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.

19void clearTheSelectedSlot(Player player) {20    PlayerInventory inventory = player.getInventory();21    int selectedSlot = inventory.getHeldItemSlot();22    inventory.clear(selectedSlot);23}
  1. Which hotbar slot, 0 to 8, the player has selected with the scroll wheel or number keys.
  2. Empties that single slot. clear() with no number empties the entire inventory.
HandAndSlots.javaCompiles on Paper 26.3Compile Lab
Where this file livesinventories-snippetssrcmainjavacomexampleinventoriessnippetsHandAndSlots.java

The package com.example.inventoriessnippets is the folder path com/example/inventoriessnippets 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.

  • inventories-snippets/
    • src/main/java/com/example/inventoriessnippets/Package com.example.inventoriessnippets
      • ArmorAndEquipment.javaHelper class
      • Containers.javaHelper class
      • CountingAndRemoving.javaHelper class
      • HandAndSlots.javayou are hereHelper class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

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.

25void giveTorchesInOffHand(Player player) {26    player.getInventory().setItemInOffHand(ItemStack.of(Material.TORCH, 16));27}
  1. The off hand (slot 40) has its own methods: getItemInOffHand and setItemInOffHand.

Armor and equipment

You can reach armor through the slot numbers 36 to 39, but the named methods are much easier to read: getHelmet, getChestplate, getLeggings, getBoots and their set twins.

ArmorAndEquipment.javaCompiles on Paper 26.3Compile Lab
Where this file livesinventories-snippetssrcmainjavacomexampleinventoriessnippetsArmorAndEquipment.java

The package com.example.inventoriessnippets is the folder path com/example/inventoriessnippets 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.

  • inventories-snippets/
    • src/main/java/com/example/inventoriessnippets/Package com.example.inventoriessnippets
      • ArmorAndEquipment.javayou are hereHelper class
      • Containers.javaHelper class
      • CountingAndRemoving.javaHelper class
      • HandAndSlots.javaHelper 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.

12void giveDiamondHelmet(Player player) {13    player.getInventory().setHelmet(ItemStack.of(Material.DIAMOND_HELMET));14}
  1. Puts a helmet on the player's head, replacing any helmet already there. The old one vanishes, so check first if that matters.
ArmorAndEquipment.javaCompiles on Paper 26.3Compile Lab
Where this file livesinventories-snippetssrcmainjavacomexampleinventoriessnippetsArmorAndEquipment.java

The package com.example.inventoriessnippets is the folder path com/example/inventoriessnippets 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.

  • inventories-snippets/
    • src/main/java/com/example/inventoriessnippets/Package com.example.inventoriessnippets
      • ArmorAndEquipment.javayou are hereHelper class
      • Containers.javaHelper class
      • CountingAndRemoving.javaHelper class
      • HandAndSlots.javaHelper 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.

16boolean isWearingHelmet(Player player) {17    ItemStack helmet = player.getInventory().getHelmet();18    return !helmet.isEmpty();19}
  1. Never null: with no helmet you get an empty stack.
  2. True only when something is actually worn.
ArmorAndEquipment.javaCompiles on Paper 26.3Compile Lab
Where this file livesinventories-snippetssrcmainjavacomexampleinventoriessnippetsArmorAndEquipment.java

The package com.example.inventoriessnippets is the folder path com/example/inventoriessnippets 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.

  • inventories-snippets/
    • src/main/java/com/example/inventoriessnippets/Package com.example.inventoriessnippets
      • ArmorAndEquipment.javayou are hereHelper class
      • Containers.javaHelper class
      • CountingAndRemoving.javaHelper class
      • HandAndSlots.javaHelper 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.

21void takeOffAllArmor(Player player) {22    PlayerInventory inventory = player.getInventory();23    inventory.setHelmet(null);24    inventory.setChestplate(null);25    inventory.setLeggings(null);26    inventory.setBoots(null);27}
  1. Passing null empties the slot. Whatever was worn is gone, not given back, so move it into the inventory first if you want to keep it.
ArmorAndEquipment.javaCompiles on Paper 26.3Compile Lab
Where this file livesinventories-snippetssrcmainjavacomexampleinventoriessnippetsArmorAndEquipment.java

The package com.example.inventoriessnippets is the folder path com/example/inventoriessnippets 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.

  • inventories-snippets/
    • src/main/java/com/example/inventoriessnippets/Package com.example.inventoriessnippets
      • ArmorAndEquipment.javayou are hereHelper class
      • Containers.javaHelper class
      • CountingAndRemoving.javaHelper class
      • HandAndSlots.javaHelper 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.

37int countArmorPieces(Player player) {38    int pieces = 0;39    for (ItemStack piece : player.getInventory().getArmorContents()) {40        if (piece != null && !piece.isEmpty()) {41            pieces++;42        }43    }44    return pieces;45}
  1. An array (a fixed-size numbered list) of the four armor slots, boots first. For players, empty slots can be null, so the loop checks for that.
  2. A for-each loop: it runs the body once for every item in the array. Loops are explained in the Java chapters.

There is a second way in: EntityEquipment. It works for players, and also for zombies, skeletons and every other mob that can wear things. It identifies slots by an EquipmentSlot name (HEAD, CHEST, LEGS, FEET, HAND, OFF_HAND), which is handy when a plugin wants one piece of code for any creature:

ArmorAndEquipment.javaCompiles on Paper 26.3Compile Lab
Where this file livesinventories-snippetssrcmainjavacomexampleinventoriessnippetsArmorAndEquipment.java

The package com.example.inventoriessnippets is the folder path com/example/inventoriessnippets 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.

  • inventories-snippets/
    • src/main/java/com/example/inventoriessnippets/Package com.example.inventoriessnippets
      • ArmorAndEquipment.javayou are hereHelper class
      • Containers.javaHelper class
      • CountingAndRemoving.javaHelper class
      • HandAndSlots.javaHelper 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.

29void fullIronSet(Player player) {30    EntityEquipment equipment = player.getEquipment();31    equipment.setItem(EquipmentSlot.HEAD, ItemStack.of(Material.IRON_HELMET));32    equipment.setItem(EquipmentSlot.CHEST, ItemStack.of(Material.IRON_CHESTPLATE));33    equipment.setItem(EquipmentSlot.LEGS, ItemStack.of(Material.IRON_LEGGINGS));34    equipment.setItem(EquipmentSlot.FEET, ItemStack.of(Material.IRON_BOOTS));35}
  1. The equipment view of this player. For a zombie you would write zombie.getEquipment() and use the rest unchanged.
  2. An enum constant naming a body slot. Reading HEAD is clearer than remembering "slot 39".

Counting, checking and removing items

Plugins constantly ask questions like "does the player have 5 diamonds?" and "take 5 diamonds from the player". Inventories have methods for both. Learn what each one really compares, because the answers are surprising.

Counting by hand

CountingAndRemoving.javaCompiles on Paper 26.3Compile Lab
Where this file livesinventories-snippetssrcmainjavacomexampleinventoriessnippetsCountingAndRemoving.java

The package com.example.inventoriessnippets is the folder path com/example/inventoriessnippets 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.

  • inventories-snippets/
    • src/main/java/com/example/inventoriessnippets/Package com.example.inventoriessnippets
      • ArmorAndEquipment.javaHelper class
      • Containers.javaHelper class
      • CountingAndRemoving.javayou are hereHelper class
      • HandAndSlots.javaHelper 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.

11int countOf(Inventory inventory, Material material) {12    int total = 0;13    for (ItemStack stack : inventory.getStorageContents()) {14        if (stack != null && stack.getType() == material) {15            total += stack.getAmount();16        }17    }18    return total;19}
  1. A running total, starting at zero.
  2. All slots where items are normally stored. For a player that is the hotbar and the main inventory, but not the armor or the off hand.
  3. Skips empty slots, then checks the material. The null check must come first, because asking a null for its type crashes.
  4. Adds this stack's amount to the total, so 3 stacks of 64 give 192.

This is the most flexible way, and it counts every diamond no matter its name or lore. Use it when you need to know "how many?".

The ready-made checks

CountingAndRemoving.javaCompiles on Paper 26.3Compile Lab
Where this file livesinventories-snippetssrcmainjavacomexampleinventoriessnippetsCountingAndRemoving.java

The package com.example.inventoriessnippets is the folder path com/example/inventoriessnippets 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.

  • inventories-snippets/
    • src/main/java/com/example/inventoriessnippets/Package com.example.inventoriessnippets
      • ArmorAndEquipment.javaHelper class
      • Containers.javaHelper class
      • CountingAndRemoving.javayou are hereHelper class
      • HandAndSlots.javaHelper 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.

21boolean hasAtLeastFiveDiamonds(Player player) {22    return player.getInventory().contains(Material.DIAMOND, 5);23}
  1. True when the diamonds in all stacks add up to at least 5. It looks at the material only.
CountingAndRemoving.javaCompiles on Paper 26.3Compile Lab
Where this file livesinventories-snippetssrcmainjavacomexampleinventoriessnippetsCountingAndRemoving.java

The package com.example.inventoriessnippets is the folder path com/example/inventoriessnippets 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.

  • inventories-snippets/
    • src/main/java/com/example/inventoriessnippets/Package com.example.inventoriessnippets
      • ArmorAndEquipment.javaHelper class
      • Containers.javaHelper class
      • CountingAndRemoving.javayou are hereHelper class
      • HandAndSlots.javaHelper 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.

25boolean hasAtLeastFivePlainDiamonds(Player player) {26    return player.getInventory().containsAtLeast(ItemStack.of(Material.DIAMOND), 5);27}
  1. Adds up stacks that are similar to the item you give, meaning the same material and the same name, lore and enchantments. A renamed diamond does not count here.

Taking items away

CountingAndRemoving.javaCompiles on Paper 26.3Compile Lab
Where this file livesinventories-snippetssrcmainjavacomexampleinventoriessnippetsCountingAndRemoving.java

The package com.example.inventoriessnippets is the folder path com/example/inventoriessnippets 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.

  • inventories-snippets/
    • src/main/java/com/example/inventoriessnippets/Package com.example.inventoriessnippets
      • ArmorAndEquipment.javaHelper class
      • Containers.javaHelper class
      • CountingAndRemoving.javayou are hereHelper class
      • HandAndSlots.javaHelper 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.

29boolean takeFiveDiamonds(Player player) {30    Map<Integer, ItemStack> missing = player.getInventory().removeItem(ItemStack.of(Material.DIAMOND, 5));31    return missing.isEmpty();32}
  1. Takes the given items out, across as many stacks as needed. It matches like containsAtLeast: plain diamonds only.
  2. Like addItem, it returns what it could not do: the part that was not found. An empty map means it removed everything.
  3. True when all five were taken.
CountingAndRemoving.javaCompiles on Paper 26.3Compile Lab
Where this file livesinventories-snippetssrcmainjavacomexampleinventoriessnippetsCountingAndRemoving.java

The package com.example.inventoriessnippets is the folder path com/example/inventoriessnippets 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.

  • inventories-snippets/
    • src/main/java/com/example/inventoriessnippets/Package com.example.inventoriessnippets
      • ArmorAndEquipment.javaHelper class
      • Containers.javaHelper class
      • CountingAndRemoving.javayou are hereHelper class
      • HandAndSlots.javaHelper 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.

34void takeEveryDiamond(Player player) {35    player.getInventory().remove(Material.DIAMOND);36}
  1. Removes every stack of that material in one go. There is no map of leftovers, it simply wipes them out.
CountingAndRemoving.javaCompiles on Paper 26.3Compile Lab
Where this file livesinventories-snippetssrcmainjavacomexampleinventoriessnippetsCountingAndRemoving.java

The package com.example.inventoriessnippets is the folder path com/example/inventoriessnippets 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.

  • inventories-snippets/
    • src/main/java/com/example/inventoriessnippets/Package com.example.inventoriessnippets
      • ArmorAndEquipment.javaHelper class
      • Containers.javaHelper class
      • CountingAndRemoving.javayou are hereHelper class
      • HandAndSlots.javaHelper 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.

38void wipeInventory(Player player) {39    player.getInventory().clear();40}
  1. Empties the whole inventory. For a player that includes armor and the off hand, so be careful with this one.
CountingAndRemoving.javaCompiles on Paper 26.3Compile Lab
Where this file livesinventories-snippetssrcmainjavacomexampleinventoriessnippetsCountingAndRemoving.java

The package com.example.inventoriessnippets is the folder path com/example/inventoriessnippets 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.

  • inventories-snippets/
    • src/main/java/com/example/inventoriessnippets/Package com.example.inventoriessnippets
      • ArmorAndEquipment.javaHelper class
      • Containers.javaHelper class
      • CountingAndRemoving.javayou are hereHelper class
      • HandAndSlots.javaHelper 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.

42int firstFreeSlot(Player player) {43    return player.getInventory().firstEmpty();44}
  1. The number of the first empty slot, or -1 when the inventory is full. Handy to check for room.

removeItem only looks in the storage slots. To also take from armor and the off hand, use removeItemAnySlot, which works exactly like removeItem but searches every slot.

Ender chests and container blocks

The ender chest belongs to the player and is the same inventory in every world, so you reach it from the player. It is an ordinary Inventory, so everything above works on it:

Containers.javaCompiles on Paper 26.3Compile Lab
Where this file livesinventories-snippetssrcmainjavacomexampleinventoriessnippetsContainers.java

The package com.example.inventoriessnippets is the folder path com/example/inventoriessnippets 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.

  • inventories-snippets/
    • src/main/java/com/example/inventoriessnippets/Package com.example.inventoriessnippets
      • ArmorAndEquipment.javaHelper class
      • Containers.javayou are hereHelper class
      • CountingAndRemoving.javaHelper class
      • HandAndSlots.javaHelper 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.

13void openEnderChest(Player player) {14    player.openInventory(player.getEnderChest());15}
  1. The player's ender chest inventory.
  2. Opens an inventory as a window on the player's screen. Any inventory works, even one you made yourself.
Containers.javaCompiles on Paper 26.3Compile Lab
Where this file livesinventories-snippetssrcmainjavacomexampleinventoriessnippetsContainers.java

The package com.example.inventoriessnippets is the folder path com/example/inventoriessnippets 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.

  • inventories-snippets/
    • src/main/java/com/example/inventoriessnippets/Package com.example.inventoriessnippets
      • ArmorAndEquipment.javaHelper class
      • Containers.javayou are hereHelper class
      • CountingAndRemoving.javaHelper class
      • HandAndSlots.javaHelper 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.

17void putStarterBreadInEnderChest(Player player) {18    player.getEnderChest().addItem(ItemStack.of(Material.BREAD, 8));19}
  1. The same addItem as before, only on a different inventory. Even if the player is not looking at it.

Chests, barrels, hoppers, furnaces and shulker boxes are blocks, so you reach their inventory through the block's state. A block state is a snapshot of the block that knows its special data. You ask whether it is a container with instanceof:

Containers.javaCompiles on Paper 26.3Compile Lab
Where this file livesinventories-snippetssrcmainjavacomexampleinventoriessnippetsContainers.java

The package com.example.inventoriessnippets is the folder path com/example/inventoriessnippets 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.

  • inventories-snippets/
    • src/main/java/com/example/inventoriessnippets/Package com.example.inventoriessnippets
      • ArmorAndEquipment.javaHelper class
      • Containers.javayou are hereHelper class
      • CountingAndRemoving.javaHelper class
      • HandAndSlots.javaHelper 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.

21Inventory inventoryOf(Block block) {22    if (block.getState() instanceof Container container) {23        return container.getInventory();24    }25    return null;26}
  1. Captures the block's current state. For a chest, that state knows about the inventory.
  2. Asks "is this state some kind of container?" and, if it is, gives it to you under the name container. This is called pattern matching. Inheritance and interfaces explains instanceof.
  3. The live inventory. Changes you make to it appear in the real chest.
  4. When the block is not a container, there is no inventory. The caller must handle null.
Containers.javaCompiles on Paper 26.3Compile Lab
Where this file livesinventories-snippetssrcmainjavacomexampleinventoriessnippetsContainers.java

The package com.example.inventoriessnippets is the folder path com/example/inventoriessnippets 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.

  • inventories-snippets/
    • src/main/java/com/example/inventoriessnippets/Package com.example.inventoriessnippets
      • ArmorAndEquipment.javaHelper class
      • Containers.javayou are hereHelper class
      • CountingAndRemoving.javaHelper class
      • HandAndSlots.javaHelper 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.

28int emptySlotsInChest(Block block) {29    if (!(block.getState() instanceof Chest chest)) {30        return 0;31    }32    int empty = 0;33    for (ItemStack stack : chest.getInventory().getContents()) {34        if (stack == null || stack.isEmpty()) {35            empty++;36        }37    }38    return empty;39}
  1. Stops early for anything that is not a chest. The "!" turns the check around, so the rest of the method only runs for chests.
  2. All slots of the chest, including empty ones (which may be null).

A double chest is one big inventory made of two chest blocks. chest.getInventory() gives you the whole thing, and chest.getBlockInventory() gives you only the half that belongs to this block:

Containers.javaCompiles on Paper 26.3Compile Lab
Where this file livesinventories-snippetssrcmainjavacomexampleinventoriessnippetsContainers.java

The package com.example.inventoriessnippets is the folder path com/example/inventoriessnippets 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.

  • inventories-snippets/
    • src/main/java/com/example/inventoriessnippets/Package com.example.inventoriessnippets
      • ArmorAndEquipment.javaHelper class
      • Containers.javayou are hereHelper class
      • CountingAndRemoving.javaHelper class
      • HandAndSlots.javaHelper 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.

41boolean isDoubleChest(Block block) {42    if (!(block.getState() instanceof Chest chest)) {43        return false;44    }45    return chest.getInventory().getSize() > chest.getBlockInventory().getSize();46}
  1. 27 for one chest half. If the whole inventory is bigger, there is a second half next to it.

Blocks themselves, such as how to find the block a player is looking at, are covered in Blocks and block data.

Inventory events

Plugins hear about everything players do with inventories through events. These four matter most:

EventFires whenCan you cancel it?
InventoryClickEventA player clicks a slot in any open inventory, including their own.Yes
InventoryDragEventA player holds a click and drags items across several slots.Yes
InventoryOpenEventA player opens a chest, furnace, menu or other window.Yes
InventoryCloseEventA player closes a window, or something closes it for them.No

Canceling a click event stops the click from doing anything, which is how plugins stop players from taking items out of a menu. That idea is the base of GUI menus.

Two inventories in one window

When a player opens a chest, the window shows two inventories at once: the chest on top and the player's own inventory below. Each has its own slot numbers, which is why a click event reports two slot numbers:

One open window = two inventories Top inventory: a small chest (27 slots) getSlot() counts 0 to 26 getRawSlot() counts 0 to 26 event.getView().getTopInventory() the chest, barrel or menu you opened Bottom inventory: the player's own inventory getSlot(): hotbar 0-8, main 9-35 getRawSlot(): main 27-53, hotbar 54-62 event.getView().getBottomInventory() raw order: chest, main inventory, hotbar Same getSlot() number in both inventories: check which one was clicked first.
One window, two inventories. getSlot() restarts in the bottom inventory, getRawSlot() keeps counting.
  • event.getSlot() is the number inside the clicked inventory, ready for getItem(slot). The same number can appear in both inventories.
  • event.getRawSlot() is unique across the whole window.
  • event.getClickedInventory() says which of the two was clicked, or is null when the click was outside the window.
  • event.getView().getTopInventory() and getBottomInventory() give the two inventories to compare against.

What was clicked, and how

The event also says what the player was doing. Use getClick() for the kind of click:

ClickTypeWhat the player did
LEFT, RIGHTNormal clicks. Left picks up or places the stack, right picks up half or places one.
SHIFT_LEFT, SHIFT_RIGHTClicked while holding Shift: the item jumps to the other inventory.
NUMBER_KEY, SWAP_OFFHANDPressed a number key 1 to 9 while hovering a slot, swapping with that hotbar slot, or pressed F to swap with the off hand.
DROP, CONTROL_DROPPressed Q (or Ctrl+Q) over a slot to throw the item out.
DOUBLE_CLICKDouble-clicked to gather matching items onto the cursor.
MIDDLE, CREATIVEMiddle clicks and creative-mode clicks.

event.getCurrentItem() is the item in the clicked slot, which can be null. event.getCursor() is the item the player is carrying on their mouse pointer.

Watch the events happen

The best way to understand them is to see them. This small plugin tells you everything that happens in the action bar and in chat while you play with inventories:

InventoryInspector.javaCompiles on Paper 26.3Compile Lab
Where this file livesinventories-inspectorsrcmainjavacomexampleinventoriesinspectorInventoryInspector.java

The package com.example.inventoriesinspector is the folder path com/example/inventoriesinspector 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.

  • inventories-inspector/
    • src/main/
      • java/com/example/inventoriesinspector/Package com.example.inventoriesinspector
        • InventoriesInspectorPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • InventoryInspector.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
    • 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.

15@EventHandler16public void onClick(InventoryClickEvent event) {17    if (!(event.getWhoClicked() instanceof Player player)) {18        return;19    }20    String where = "outside";21    if (event.getClickedInventory() != null) {22        where = event.getClickedInventory() == event.getView().getTopInventory() ? "top" : "bottom";23    }24    ItemStack clicked = event.getCurrentItem();25    String item = clicked == null || clicked.isEmpty() ? "nothing" : clicked.getType().name();26    player.sendRichMessage("<gray>Click <white><click></white> in the <white><where></white> inventory, slot <white><slot></white> (raw <white><raw></white>) on <white><item>",27        Placeholder.unparsed("click", event.getClick().name()),28        Placeholder.unparsed("where", where),29        Placeholder.unparsed("slot", String.valueOf(event.getSlot())),30        Placeholder.unparsed("raw", String.valueOf(event.getRawSlot())),31        Placeholder.unparsed("item", item));32}
  1. Clickers are HumanEntity objects; this check keeps only players, which is what the messages are sent to.
  2. Starts with "outside". It only changes if the click was inside a window.
  3. A click outside the window gives null, so we must check before using it.
  4. Compares the clicked inventory with the top one. If it is not the top one, it is the bottom one.
  5. An empty slot can be null or empty, so check both.
  6. The click type as text, such as SHIFT_LEFT.
  7. The window-wide slot number.
InventoryInspector.javaCompiles on Paper 26.3Compile Lab
Where this file livesinventories-inspectorsrcmainjavacomexampleinventoriesinspectorInventoryInspector.java

The package com.example.inventoriesinspector is the folder path com/example/inventoriesinspector 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.

  • inventories-inspector/
    • src/main/
      • java/com/example/inventoriesinspector/Package com.example.inventoriesinspector
        • InventoriesInspectorPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • InventoryInspector.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
    • 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.

46@EventHandler47public void onClose(InventoryCloseEvent event) {48    event.getPlayer().sendRichMessage("<red>Closed it because: <reason>",49        Placeholder.unparsed("reason", event.getReason().name()));50}
  1. Why the window closed: the player pressed Esc (PLAYER), another window replaced it (OPEN_NEW), they teleported, died, disconnected, or a plugin closed it.

Open a chest, shift-click a stack into it, press a number key over a slot, and read what the plugin tells you. The file also has small onDrag and onOpen handlers that work the same way, so you will see lines for those too:

Opened a CHEST inventory
Click LEFT in the top inventory, slot 4 (raw 4) on nothing
Click SHIFT_LEFT in the bottom inventory, slot 2 (raw 56) on IRON_INGOT
Click NUMBER_KEY in the top inventory, slot 11 (raw 11) on BREAD
Closed it because: PLAYER

Notice that the shift-click shows the bottom inventory's slot 2 with raw slot 56. Slot 2 of the hotbar is 54 plus 2 in the window numbering, because the raw order is chest (0 to 26 for a one-chest window), then main inventory (27 to 53), then hotbar (54 to 62).

Example plugin: /sell and /trash

Time to build something. /sell turns the item in your hand into experience. /sell all sells every plain item of that kind in your inventory. /trash opens a bin that destroys whatever is in it when you close it.

  • inventories-demo/
    • src/
      • main/
        • java/
          • com/example/inventoriesdemo/
            • InventoriesDemoPlugin.javaRegisters both commands and the trash listener
            • SellPrices.javaWhich items can be sold and for how much experience
            • SellCommand.javaThe /sell command
            • TrashHolder.javaMarks an inventory as "the trash bin"
            • TrashCommand.javaThe /trash command: opens the bin
            • TrashListener.javaEmpties the bin when it is closed
        • resources/
          • plugin.ymlPlugin name, main class and permissions

First the price list. SellPrices holds a map from material to experience points, and two helpers that use it:

SellPrices.javaCompiles on Paper 26.3Compile Lab
Where this file livesinventories-demosrcmainjavacomexampleinventoriesdemoSellPrices.java

The package com.example.inventoriesdemo is the folder path com/example/inventoriesdemo 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.

  • inventories-demo/
    • src/main/
      • java/com/example/inventoriesdemo/Package com.example.inventoriesdemo
        • InventoriesDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • SellCommand.javaCommand (BasicCommand)
        • SellPrices.javayou are hereHelper class
        • TrashCommand.javaCommand (BasicCommand)
        • TrashHolder.javaMenu (inventory holder)
        • TrashListener.javaListener: reacts to events
      • 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.

20static boolean isSellable(ItemStack item) {21    return XP_PER_ITEM.containsKey(item.getType()) && item.isSimilar(ItemStack.of(item.getType()));22}
  1. The material must be on the price list.
  2. The stack must also be a plain item with no custom name or lore. Otherwise players could sell your special quest items.
SellPrices.javaCompiles on Paper 26.3Compile Lab
Where this file livesinventories-demosrcmainjavacomexampleinventoriesdemoSellPrices.java

The package com.example.inventoriesdemo is the folder path com/example/inventoriesdemo 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.

  • inventories-demo/
    • src/main/
      • java/com/example/inventoriesdemo/Package com.example.inventoriesdemo
        • InventoriesDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • SellCommand.javaCommand (BasicCommand)
        • SellPrices.javayou are hereHelper class
        • TrashCommand.javaCommand (BasicCommand)
        • TrashHolder.javaMenu (inventory holder)
        • TrashListener.javaListener: reacts to events
      • 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.

24static int xpFor(ItemStack item) {25    return XP_PER_ITEM.getOrDefault(item.getType(), 0) * item.getAmount();26}
  1. Looks up the price, using 0 when the material is not on the list.
  2. The whole stack is paid: price per item times amount.

The command sells either the held stack or, with the word all, everything of that kind:

SellCommand.javaCompiles on Paper 26.3Compile Lab
Where this file livesinventories-demosrcmainjavacomexampleinventoriesdemoSellCommand.java

The package com.example.inventoriesdemo is the folder path com/example/inventoriesdemo 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.

  • inventories-demo/
    • src/main/
      • java/com/example/inventoriesdemo/Package com.example.inventoriesdemo
        • InventoriesDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • SellCommand.javayou are hereCommand (BasicCommand)
        • SellPrices.javaHelper class
        • TrashCommand.javaCommand (BasicCommand)
        • TrashHolder.javaMenu (inventory holder)
        • TrashListener.javaListener: reacts to events
      • 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.

12@Override13public void execute(CommandSourceStack source, String[] args) {14    if (!(source.getExecutor() instanceof Player player)) {15        source.getSender().sendRichMessage("<red>Only players can sell items.");16        return;17    }18    if (args.length > 0 && args[0].equalsIgnoreCase("all")) {19        sellAllOfTheHeldKind(player);20    } else {21        sellHeldStack(player);22    }23}
  1. Only players have a hand and an inventory.
  2. The first word after /sell. args.length > 0 comes first, to avoid reading a word that is not there.
SellCommand.javaCompiles on Paper 26.3Compile Lab
Where this file livesinventories-demosrcmainjavacomexampleinventoriesdemoSellCommand.java

The package com.example.inventoriesdemo is the folder path com/example/inventoriesdemo 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.

  • inventories-demo/
    • src/main/
      • java/com/example/inventoriesdemo/Package com.example.inventoriesdemo
        • InventoriesDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • SellCommand.javayou are hereCommand (BasicCommand)
        • SellPrices.javaHelper class
        • TrashCommand.javaCommand (BasicCommand)
        • TrashHolder.javaMenu (inventory holder)
        • TrashListener.javaListener: reacts to events
      • 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.

25private void sellHeldStack(Player player) {26    ItemStack held = player.getInventory().getItemInMainHand();27    if (held.isEmpty() || !SellPrices.isSellable(held)) {28        tellNotSellable(player);29        return;30    }31    int xp = SellPrices.xpFor(held);32    int amount = held.getAmount();33    player.getInventory().setItemInMainHand(ItemStack.empty());34    pay(player, amount, xp);35}
  1. Refuse when the hand is empty or the item is not on the list. The empty check comes first, because it is the cheapest.
  2. Work out the payment before removing the item, because the live stack changes when you clear the hand.
  3. Takes the whole stack out of the hand.
SellCommand.javaCompiles on Paper 26.3Compile Lab
Where this file livesinventories-demosrcmainjavacomexampleinventoriesdemoSellCommand.java

The package com.example.inventoriesdemo is the folder path com/example/inventoriesdemo 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.

  • inventories-demo/
    • src/main/
      • java/com/example/inventoriesdemo/Package com.example.inventoriesdemo
        • InventoriesDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • SellCommand.javayou are hereCommand (BasicCommand)
        • SellPrices.javaHelper class
        • TrashCommand.javaCommand (BasicCommand)
        • TrashHolder.javaMenu (inventory holder)
        • TrashListener.javaListener: reacts to events
      • 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.

37private void sellAllOfTheHeldKind(Player player) {38    ItemStack held = player.getInventory().getItemInMainHand();39    if (held.isEmpty() || !SellPrices.isSellable(held)) {40        tellNotSellable(player);41        return;42    }43    ItemStack kind = ItemStack.of(held.getType());44    Inventory inventory = player.getInventory();45    int amount = 0;46    int xp = 0;47    ItemStack[] contents = inventory.getStorageContents();48    for (int slot = 0; slot < contents.length; slot++) {49        ItemStack stack = contents[slot];50        if (stack != null && stack.isSimilar(kind)) {51            amount += stack.getAmount();52            xp += SellPrices.xpFor(stack);53            inventory.clear(slot);54        }55    }56    pay(player, amount, xp);57}
  1. Remembers a plain item of the held kind. Without this copy, clearing the held slot would change held in the middle of the loop, because for players it is a live mirror.
  2. A snapshot of all the storage slots, with the slot number being the position in the array.
  3. Skips empty slots, and sells only plain items of the same kind.
  4. Empties that single slot. Using the slot number from the loop makes sure only matching stacks disappear.
SellCommand.javaCompiles on Paper 26.3Compile Lab
Where this file livesinventories-demosrcmainjavacomexampleinventoriesdemoSellCommand.java

The package com.example.inventoriesdemo is the folder path com/example/inventoriesdemo 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.

  • inventories-demo/
    • src/main/
      • java/com/example/inventoriesdemo/Package com.example.inventoriesdemo
        • InventoriesDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • SellCommand.javayou are hereCommand (BasicCommand)
        • SellPrices.javaHelper class
        • TrashCommand.javaCommand (BasicCommand)
        • TrashHolder.javaMenu (inventory holder)
        • TrashListener.javaListener: reacts to events
      • 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.

59private void pay(Player player, int amount, int xp) {60    player.giveExp(xp);61    player.sendRichMessage("<green>Sold <amount> item(s) for <xp> xp.",62        Placeholder.unparsed("amount", String.valueOf(amount)),63        Placeholder.unparsed("xp", String.valueOf(xp)));64}
  1. Adds experience points to the player, like picking up orbs.

The trash bin needs a way to recognize its inventory when it closes. The player's own inventory and every chest also send close events, and you must not wipe those! Bukkit lets an inventory have a holder, an object that owns it. We make a tiny holder class just to act as a name tag:

TrashHolder.javaCompiles on Paper 26.3Compile Lab
Where this file livesinventories-demosrcmainjavacomexampleinventoriesdemoTrashHolder.java

The package com.example.inventoriesdemo is the folder path com/example/inventoriesdemo 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.

  • inventories-demo/
    • src/main/
      • java/com/example/inventoriesdemo/Package com.example.inventoriesdemo
        • InventoriesDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • SellCommand.javaCommand (BasicCommand)
        • SellPrices.javaHelper class
        • TrashCommand.javaCommand (BasicCommand)
        • TrashHolder.javayou are hereMenu (inventory holder)
        • TrashListener.javaListener: reacts to events
      • 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.inventoriesdemo;2 3import net.kyori.adventure.text.Component;4import net.kyori.adventure.text.format.NamedTextColor;5import org.bukkit.Bukkit;6import org.bukkit.inventory.Inventory;7import org.bukkit.inventory.InventoryHolder;8 9final class TrashHolder implements InventoryHolder {10 11    private final Inventory inventory;12 13    TrashHolder() {14        this.inventory = Bukkit.createInventory(this, 27, Component.text("Trash: closing destroys everything", NamedTextColor.DARK_RED));15    }16 17    @Override18    public Inventory getInventory() {19        return inventory;20    }21}
  1. Promises that this class can own an inventory. It has one required method, getInventory.
  2. Creates a brand-new inventory with 27 slots (3 rows). The first argument, this, makes this object the holder. The text is the window title.
  3. The title is a component, like every text in modern Paper.
  4. Returns the inventory this holder owns. createInventory already linked the two, and this method lets other code go from holder to inventory.
TrashCommand.javaCompiles on Paper 26.3Compile Lab
Where this file livesinventories-demosrcmainjavacomexampleinventoriesdemoTrashCommand.java

The package com.example.inventoriesdemo is the folder path com/example/inventoriesdemo 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.

  • inventories-demo/
    • src/main/
      • java/com/example/inventoriesdemo/Package com.example.inventoriesdemo
        • InventoriesDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • SellCommand.javaCommand (BasicCommand)
        • SellPrices.javaHelper class
        • TrashCommand.javayou are hereCommand (BasicCommand)
        • TrashHolder.javaMenu (inventory holder)
        • TrashListener.javaListener: reacts to events
      • 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.

9@Override10public void execute(CommandSourceStack source, String[] args) {11    if (source.getExecutor() instanceof Player player) {12        player.openInventory(new TrashHolder().getInventory());13    } else {14        source.getSender().sendRichMessage("<red>Only players have a trash can.");15    }16}
  1. Every time someone types /trash, we create a fresh bin so two players never share one.
TrashListener.javaCompiles on Paper 26.3Compile Lab
Where this file livesinventories-demosrcmainjavacomexampleinventoriesdemoTrashListener.java

The package com.example.inventoriesdemo is the folder path com/example/inventoriesdemo 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.

  • inventories-demo/
    • src/main/
      • java/com/example/inventoriesdemo/Package com.example.inventoriesdemo
        • InventoriesDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • SellCommand.javaCommand (BasicCommand)
        • SellPrices.javaHelper class
        • TrashCommand.javaCommand (BasicCommand)
        • TrashHolder.javaMenu (inventory holder)
        • TrashListener.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
    • 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.

13@EventHandler14public void onClose(InventoryCloseEvent event) {15    Inventory closed = event.getInventory();16    if (!(closed.getHolder(false) instanceof TrashHolder)) {17        return;18    }19    int destroyed = 0;20    for (ItemStack stack : closed.getContents()) {21        if (stack != null) {22            destroyed += stack.getAmount();23        }24    }25    closed.clear();26    if (destroyed > 0 && event.getPlayer() instanceof Player player) {27        player.sendRichMessage("<gray>Destroyed <count> item(s).",28            Placeholder.unparsed("count", String.valueOf(destroyed)));29    }30}
  1. Is the inventory that just closed a trash bin? Only then do we continue. The false means "do not copy block data just to look".
  2. Empty slots in the contents array can be null.
  3. This is the "destroy": everything in the bin is erased.
  4. The event only promises a HumanEntity, so we check that it is a player before messaging.
InventoriesDemoPlugin.javaCompiles on Paper 26.3Compile Lab
Where this file livesinventories-demosrcmainjavacomexampleinventoriesdemoInventoriesDemoPlugin.java

The package com.example.inventoriesdemo is the folder path com/example/inventoriesdemo 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.

  • inventories-demo/
    • src/main/
      • java/com/example/inventoriesdemo/Package com.example.inventoriesdemo
        • InventoriesDemoPlugin.javayou are hereMain class: Paper starts here (named as main in plugin.yml)
        • SellCommand.javaCommand (BasicCommand)
        • SellPrices.javaHelper class
        • TrashCommand.javaCommand (BasicCommand)
        • TrashHolder.javaMenu (inventory holder)
        • TrashListener.javaListener: reacts to events
      • 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.inventoriesdemo;2 3import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents;4import org.bukkit.plugin.java.JavaPlugin;5 6public final class InventoriesDemoPlugin extends JavaPlugin {7 8    @Override9    public void onEnable() {10        getServer().getPluginManager().registerEvents(new TrashListener(), this);11        getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, event -> {12            event.registrar().register("sell", "Sell the item in your hand for experience", new SellCommand());13            event.registrar().register("trash", "Open a bin that destroys what you put in it", new TrashCommand());14        });15    }16}
  1. The listener must be registered, or the bin never empties.
  2. Registers /sell.
  3. Registers /trash.
plugin.ymlCompiles on Paper 26.3Compile Lab
Where this file livesinventories-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.

  • inventories-demo/
    • src/main/
      • java/com/example/inventoriesdemo/Package com.example.inventoriesdemo
        • InventoriesDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • SellCommand.javaCommand (BasicCommand)
        • SellPrices.javaHelper class
        • TrashCommand.javaCommand (BasicCommand)
        • TrashHolder.javaMenu (inventory holder)
        • TrashListener.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
    • 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.

1name: InventoriesDemo2version: '1.0.0'3main: com.example.inventoriesdemo.InventoriesDemoPlugin4api-version: '26.3'5description: A /sell command that trades held items for experience and a /trash bin that destroys items.6permissions:7  inventoriesdemo.sell:8    description: Lets a player use /sell.9    default: true10  inventoriesdemo.trash:11    description: Lets a player use /trash.12    default: true
  1. One permission per command, both allowed for everybody by default.

Hold five diamonds and type /sell:

Sold 5 item(s) for 250 xp.

Type /sell while holding a sword, and you learn what is for sale:

Hold something sellable. Prices: coal = 2 xp, iron_ingot = 5 xp, gold_ingot = 8 xp, emerald = 30 xp, diamond = 50 xp

And /trash opens a window like this one. Shift-click anything from your inventory into it, then close it with Esc:

10: rotten_flesh, 11: spider_eye, 12: cobblestone

You can download the plugin as a Gradle project, run it, or try the inspector plugin next to it.

Mistakes to avoid

  • Treating an empty slot as always null. Slots can be null or an empty stack. Check item == null || item.isEmpty().
  • Forgetting that addItem and removeItem return what they could not do. Ignoring the map loses items or lets players buy things they cannot afford.
  • Removing before checking. removeItem removes as much as it can and stops, so a player with 3 of the 5 items you wanted already lost 3. Check first with containsAtLeast.
  • Comparing slots with the wrong numbers. In a click event, getSlot() belongs to the clicked inventory and getRawSlot() to the whole window. Check getClickedInventory() first.
  • Counting only getType() when you meant plain items. Named or enchanted items of that material also match. Use isSimilar against a plain item when only plain ones count.
  • Mixing up contains(ItemStack) and containsAtLeast. The first one needs one stack with exactly that amount.
Try it

Pay with iron

Write boolean payWithIron(Player player, int price). It should take exactly price iron ingots from the player and return true. If the player does not have enough, it should tell them and return false, and it must not take anything.

Hint 1

Ask first with containsAtLeast(ItemStack.of(Material.IRON_INGOT), price). Only remove the items when that is true.

Hint 2

The removal is player.getInventory().removeItem(ItemStack.of(Material.IRON_INGOT, price)).

Show the solution

The check goes first, so a player with too few ingots loses nothing. Only when the check passes does the method remove the ingots.

Purchase.javaCompiles on Paper 26.3Compile Lab
Where this file livesinventories-exercisesrcmainjavacomexampleinventoriesexercisePurchase.java

The package com.example.inventoriesexercise is the folder path com/example/inventoriesexercise 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.

  • inventories-exercise/
    • src/main/java/com/example/inventoriesexercise/Package com.example.inventoriesexercise
      • HelmetIfEmpty.javaHelper class
      • Purchase.javayou are hereHelper class
    • build.gradle.ktsThe build recipe: Paper 26.3 API, Java 25, how the jar is made
    • gradle.propertiesVersion numbers used by the build
    • gradlew.batRuns Gradle on Windows without installing it
    • settings.gradle.ktsThe project's name

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.

9boolean payWithIron(Player player, int price) {10    ItemStack payment = ItemStack.of(Material.IRON_INGOT);11    if (!player.getInventory().containsAtLeast(payment, price)) {12        player.sendRichMessage("<red>You need " + price + " iron ingots.");13        return false;14    }15    player.getInventory().removeItem(ItemStack.of(Material.IRON_INGOT, price));16    return true;17}
  1. Adds up all plain iron ingots in the inventory.
  2. Leaves early without touching the inventory.
  3. Takes the ingots only after the check passed.
Try it

Pumpkin helmet, politely

Write boolean equipPumpkin(Player player). It puts a carved pumpkin on the player's head, but only when the helmet slot is empty. It returns true when it equipped the pumpkin and false when the player was already wearing something.

Hint

Remember that getHelmet() never returns null: an empty slot is an empty stack.

Show the solution

Read the current helmet, stop early if it is not empty, and set the pumpkin otherwise.

HelmetIfEmpty.javaCompiles on Paper 26.3Compile Lab
Where this file livesinventories-exercisesrcmainjavacomexampleinventoriesexerciseHelmetIfEmpty.java

The package com.example.inventoriesexercise is the folder path com/example/inventoriesexercise 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.

  • inventories-exercise/
    • src/main/java/com/example/inventoriesexercise/Package com.example.inventoriesexercise
      • HelmetIfEmpty.javayou are hereHelper class
      • Purchase.javaHelper 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.

9boolean equipPumpkin(Player player) {10    ItemStack current = player.getInventory().getHelmet();11    if (!current.isEmpty()) {12        return false;13    }14    player.getInventory().setHelmet(ItemStack.of(Material.CARVED_PUMPKIN));15    return true;16}
  1. An empty stack if nothing is worn.
  2. Something is worn, so leave it alone and report that nothing happened.
  3. The pumpkin you can wear.

Recap

  • An Inventory is numbered slots starting at 0. A player's hotbar is 0 to 8, the main inventory 9 to 35, armor 36 to 39 and the off hand is 40.
  • An empty slot may be null or an empty stack. Check both. getItemInMainHand() never returns null.
  • getHelmet and friends, or EntityEquipment with EquipmentSlot, reach the armor and the hands of players and mobs.
  • contains(Material, amount) and containsAtLeast count; removeItem removes and returns what it could not remove; remove(Material), clear(slot) and clear() wipe things out.
  • Reach a chest's inventory through its block state, and a player's ender chest with getEnderChest().
  • Inventory events: click, drag, open and close. A window holds two inventories, so getSlot() and getRawSlot() differ.
  • An InventoryHolder lets you recognize an inventory you made yourself.

Quick quiz

  1. Which slot number is the helmet in a PlayerInventory?

  2. You call player.getInventory().getItem(5) and get back null. What does that most likely mean?

  3. A player has 3 diamonds. You call removeItem(ItemStack.of(Material.DIAMOND, 5)). What happens?

  4. In an InventoryClickEvent, what does getClickedInventory() tell you?

  5. Why does the /trash example use a TrashHolder class?

Next steps