Paper Plugin Guide
File mode0

Going further

Registries, keys and data components

How Paper names game content with keys, and how data-driven items and registries work.

Advanced54 min read

Everything in Minecraft has a name: every item, block, enchantment and sound. On this page you will learn how Paper stores those names in registries, how to look things up safely, how an item really keeps its data in data components, and how a plugin can add a brand new enchantment.

Everything has a key

When you type /give @s minecraft:diamond_sword, the text minecraft:diamond_sword is not just a label. It is a key: the one official name of the diamond sword. A key has two halves, split by a colon.

  • The namespace (before the colon) says who owns the name. For everything in vanilla Minecraft it is minecraft. A plugin uses its own name, so two plugins can both have a "magic_wand" without clashing.
  • The value (after the colon) says what the thing is, such as diamond_sword.

Namespaces and values may only contain lowercase letters, digits, underscores, hyphens and periods. Values may also contain forward slashes. Capital letters or spaces are errors.

Three key classes, one idea

Paper has three Java classes for keys. They all describe the same thing, and you will meet all of them in tutorials, so it is worth knowing which is which.

ClassWhat it isWhen you use it
Key (net.kyori.adventure.key)The modern key class from the Adventure library.Your default choice for new code. Most Paper methods accept it.
NamespacedKey (org.bukkit)Bukkit's older key class. It is a Key, so it works wherever a Key is wanted.Persistent data, recipes and other older APIs still ask for it.
TypedKey<T> (io.papermc.paper.registry)A Key that also remembers which registry it belongs to.Registry events and ready-made constants like EnchantmentKeys.SHARPNESS.

Here is how you make each kind. Read the notes, then hover any word to see what it is.

KeySnippets.javaCompiles on Paper 26.3Compile Lab
Where this file livesregistries-data-components-snippetssrcmainjavacomexampleregistriesdatacomponentssnippetsKeySnippets.java

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

  • registries-data-components-snippets/
    • src/main/java/com/example/registriesdatacomponentssnippets/Package com.example.registriesdatacomponentssnippets
      • ComponentSnippets.javaHelper class
      • KeySnippets.javayou are hereHelper class
      • RegistrySnippets.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.

16static void makeKeys(Plugin plugin) {17    Key vanillaSword = Key.key("minecraft", "diamond_sword");18    Key sameSword = Key.key("diamond_sword");19    Key fromText = Key.key("mything:magic_wand");20 21    NamespacedKey bukkitStyle = NamespacedKey.minecraft("diamond_sword");22    NamespacedKey ownedByMe = new NamespacedKey(plugin, "magic_wand");23 24    plugin.getLogger().info(vanillaSword.asString());25    plugin.getLogger().info(String.valueOf(vanillaSword.equals(sameSword)));26    plugin.getLogger().info(fromText.namespace() + " / " + fromText.value());27    plugin.getLogger().info(bukkitStyle.asString() + " and " + ownedByMe.asString());28}
  1. The namespace and the value as two separate strings.
  2. With no colon and no namespace, Key.key fills in minecraft for you. This is the same key as the line above.
  3. One string with a colon: Java splits it into namespace mything and value magic_wand.
  4. A shortcut for a vanilla key in the older NamespacedKey class.
  5. Uses your plugin's name, in lowercase, as the namespace. This is the right choice for your own custom data.
  6. Prints true. Keys compare by their text, so two keys with the same namespace and value are equal, whichever class made them.
  7. namespace() gives the part before the colon and value() the part after it.

Registries: the server's phone books

A registry is a lookup table. You give it a key, and it gives back the object with that name. The server keeps one registry for items, another for blocks, another for enchantments, sounds, biomes, potion effects and so on. They are separate on purpose: minecraft:sharpness exists in the enchantment registry and nowhere else.

A key points to one entry in a registry The key minecraft:diamond_sword has a namespace and a value. The item registry is a lookup table from keys to item types, and the block registry is a separate table. A key is the name, a registry is the phone book minecraft:diamond_sword the key, as one string minecraft diamond_sword namespace value who owns it : what it is get(key) Item registry (RegistryKey.ITEM) minecraft:stick ItemType minecraft:diamond_sword ItemType guide:magic_wand (if a plugin added it) Block registry (RegistryKey.BLOCK) another table, with its own entries Enchantment registry same key shape, different table
A key is only a name. Each registry is a table that turns names into real objects, and every table is separate.

To get a registry you need to say which one you want. Each registry has a name of its own, a RegistryKey, such as RegistryKey.ENCHANTMENT. You hand that to RegistryAccess, Paper's front door to all registries:

RegistrySnippets.javaCompiles on Paper 26.3Compile Lab
Where this file livesregistries-data-components-snippetssrcmainjavacomexampleregistriesdatacomponentssnippetsRegistrySnippets.java

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

  • registries-data-components-snippets/
    • src/main/java/com/example/registriesdatacomponentssnippets/Package com.example.registriesdatacomponentssnippets
      • ComponentSnippets.javaHelper class
      • KeySnippets.javaHelper class
      • RegistrySnippets.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.

20static Registry<Enchantment> enchantments() {21    return RegistryAccess.registryAccess().getRegistry(RegistryKey.ENCHANTMENT);22}
  1. The static method that gives you the front door. There is only one, so you never create it yourself.
  2. Asks for the enchantment registry. The result is a Registry<Enchantment>, a table whose entries are enchantments.

Looking things up

Once you have a registry, get turns a key into an object. If nothing has that name, get returns null, so always check, or use getOrThrow when a missing entry would be your own bug.

RegistrySnippets.javaCompiles on Paper 26.3Compile Lab
Where this file livesregistries-data-components-snippetssrcmainjavacomexampleregistriesdatacomponentssnippetsRegistrySnippets.java

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

  • registries-data-components-snippets/
    • src/main/java/com/example/registriesdatacomponentssnippets/Package com.example.registriesdatacomponentssnippets
      • ComponentSnippets.javaHelper class
      • KeySnippets.javaHelper class
      • RegistrySnippets.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.

32static Enchantment maybeMissing(String name) {33    Enchantment found = enchantments().get(Key.key(name));34    if (found == null) {35        throw new IllegalArgumentException("No enchantment called " + name);36    }37    return found;38}
  1. Looks the name up. Returns null when no enchantment has that key.
  2. The check that stops a NullPointerException later. Tell the player or throw a clear error here instead.

A registry is also a list you can walk through. It can give you all its keys, and you can use it directly in a for loop:

RegistrySnippets.javaCompiles on Paper 26.3Compile Lab
Where this file livesregistries-data-components-snippetssrcmainjavacomexampleregistriesdatacomponentssnippetsRegistrySnippets.java

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

  • registries-data-components-snippets/
    • src/main/java/com/example/registriesdatacomponentssnippets/Package com.example.registriesdatacomponentssnippets
      • ComponentSnippets.javaHelper class
      • KeySnippets.javaHelper class
      • RegistrySnippets.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.

40static List<String> allEnchantmentNames() {41    return enchantments().keyStream()42        .map(Key::asMinimalString)43        .sorted()44        .toList();45}
  1. All keys in the registry, as a stream (a pipeline of values you can filter and sort).
  2. Turns each key into text and leaves out minecraft:, so you get sharpness instead of minecraft:sharpness.
RegistrySnippets.javaCompiles on Paper 26.3Compile Lab
Where this file livesregistries-data-components-snippetssrcmainjavacomexampleregistriesdatacomponentssnippetsRegistrySnippets.java

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

  • registries-data-components-snippets/
    • src/main/java/com/example/registriesdatacomponentssnippets/Package com.example.registriesdatacomponentssnippets
      • ComponentSnippets.javaHelper class
      • KeySnippets.javaHelper class
      • RegistrySnippets.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.

47static int countEnchantmentsThatReachLevelFive() {48    int count = 0;49    for (Enchantment enchantment : enchantments()) {50        if (enchantment.getMaxLevel() >= 5) {51            count++;52        }53    }54    return count;55}
  1. A Registry is Iterable, so the enhanced for loop visits every entry, one after another.

For the big, data-driven registries, Paper also ships ready-made typed keys, so you never have to type the text yourself and typos become compile errors:

RegistrySnippets.javaCompiles on Paper 26.3Compile Lab
Where this file livesregistries-data-components-snippetssrcmainjavacomexampleregistriesdatacomponentssnippetsRegistrySnippets.java

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

  • registries-data-components-snippets/
    • src/main/java/com/example/registriesdatacomponentssnippets/Package com.example.registriesdatacomponentssnippets
      • ComponentSnippets.javaHelper class
      • KeySnippets.javaHelper class
      • RegistrySnippets.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.

28static Enchantment sharpnessWithTypedKey() {29    return enchantments().getOrThrow(EnchantmentKeys.SHARPNESS);30}
  1. A TypedKey<Enchantment> that Paper made for you. IntelliJ autocompletes the whole list when you type EnchantmentKeys..
  2. Because the key is typed, the compiler knows the result is an Enchantment and not, say, an item.

ItemType and BlockType versus Material

You already know Material, the long list with DIAMOND_SWORD and STONE in it. Material is the old way to say "which kind of thing". It has a flaw: one list has to describe both items and blocks, so it holds names that make no sense as an item (like WALL_TORCH) or as a block (like DIAMOND_SWORD).

Paper's modern answer is two separate types, both found in registries:

  • ItemType: a kind of item. It knows the item's defaults, for example its maximum stack size, and can create stacks. Constants look like ItemType.DIAMOND_SWORD.
  • BlockType: a kind of block. It knows how hard it is to break, whether it is solid, and what BlockData it can have. Constants look like BlockType.STONE.

A block you can hold has both: BlockType.STONE is the placed block and ItemType.STONE is the item in your hand. They are connected, as these helpers show:

RegistrySnippets.javaCompiles on Paper 26.3Compile Lab
Where this file livesregistries-data-components-snippetssrcmainjavacomexampleregistriesdatacomponentssnippetsRegistrySnippets.java

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

  • registries-data-components-snippets/
    • src/main/java/com/example/registriesdatacomponentssnippets/Package com.example.registriesdatacomponentssnippets
      • ComponentSnippets.javaHelper class
      • KeySnippets.javaHelper class
      • RegistrySnippets.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.

65static BlockType blockOfAnItem(ItemStack item) {66    ItemType itemType = item.getType().asItemType();67    if (itemType == null || !itemType.hasBlockType()) {68        return null;69    }70    return itemType.getBlockType();71}
  1. Converts the old Material into an ItemType. This is the bridge from old code to new code.
  2. Not every item is a block. A diamond sword has no block form, so check first.
  3. The block this item places.

You can create an item straight from its type, and compare types with ==:

RegistrySnippets.javaCompiles on Paper 26.3Compile Lab
Where this file livesregistries-data-components-snippetssrcmainjavacomexampleregistriesdatacomponentssnippetsRegistrySnippets.java

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

  • registries-data-components-snippets/
    • src/main/java/com/example/registriesdatacomponentssnippets/Package com.example.registriesdatacomponentssnippets
      • ComponentSnippets.javaHelper class
      • KeySnippets.javaHelper class
      • RegistrySnippets.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.

61static ItemStack swordFromItemType() {62    return ItemType.DIAMOND_SWORD.createItemStack();63}
  1. Makes a stack of one item. createItemStack(16) makes sixteen.

Do you have to stop using Material? No. It is still everywhere in the API and in every tutorial, and ItemStack.of(Material.DIAMOND_SWORD) is perfectly fine. Use ItemType and BlockType when you want the extra knowledge they carry, such as defaults, or when code needs to be clear about "item" versus "block". Note that ItemType#asMaterial() is marked "only for internal use" and deprecated, so go from Material to the new types (asItemType(), asBlockType()), not the other way around.

Data components: how an item remembers things

On the Items page you used data components to give an item a name and lore. Now you will see the whole picture. A modern item is just a type (minecraft:apple) plus a set of data components. Each component is one labeled piece of information: the name, the lore, the enchantments, the maximum stack size, the food values, the durability and so on. Paper 26.3 has more than a hundred data component types, and DataComponentTypes has a constant for each one you can set from a plugin.

One ItemStack = one slot's worth of stuff ItemStack sword = ItemStack.of(Material.DIAMOND_SWORD); Material which kind of item DIAMOND_SWORD Amount how many in the stack 1 Data components everything else about it name, lore, enchantments... CUSTOM_NAME the title LORE story lines ENCHANTMENTS Sharpness V MAX_STACK_SIZE and many more
An ItemStack is a type, an amount, and a bundle of data components.

Valued and non-valued

Components come in two shapes, and the Java types tell you which is which:

  • A valued component carries a value. DataComponentTypes.MAX_STACK_SIZE is a DataComponentType.Valued<Integer>: the value is a whole number. CUSTOM_NAME is Valued<Component>: the value is text. The part in angle brackets is the kind of value.
  • A non-valued component is only on or off. DataComponentTypes.UNBREAKABLE is a DataComponentType.NonValued. There is nothing to store except "this item has it".

The two shapes use different methods, and the compiler holds you to it:

ComponentSnippets.javaCompiles on Paper 26.3Compile Lab
Where this file livesregistries-data-components-snippetssrcmainjavacomexampleregistriesdatacomponentssnippetsComponentSnippets.java

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

  • registries-data-components-snippets/
    • src/main/java/com/example/registriesdatacomponentssnippets/Package com.example.registriesdatacomponentssnippets
      • ComponentSnippets.javayou are hereHelper class
      • KeySnippets.javaHelper class
      • RegistrySnippets.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.

23static ItemStack nameTheItem(ItemStack item) {24    item.setData(DataComponentTypes.CUSTOM_NAME, Component.text("Lucky Stick", NamedTextColor.GOLD));25    return item;26}
  1. A valued type, so setData needs a second argument: the value.
  2. The value must be a Component, because CUSTOM_NAME is Valued<Component>. Passing a plain String will not compile.
ComponentSnippets.javaCompiles on Paper 26.3Compile Lab
Where this file livesregistries-data-components-snippetssrcmainjavacomexampleregistriesdatacomponentssnippetsComponentSnippets.java

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

  • registries-data-components-snippets/
    • src/main/java/com/example/registriesdatacomponentssnippets/Package com.example.registriesdatacomponentssnippets
      • ComponentSnippets.javayou are hereHelper class
      • KeySnippets.javaHelper class
      • RegistrySnippets.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.

28static ItemStack makeUnbreakable(ItemStack item) {29    item.setData(DataComponentTypes.UNBREAKABLE);30    return item;31}
  1. A non-valued type, so there is no value to give. Just naming the component turns it on.

Reading follows the same split. getData works only for valued types and returns the value, or null when the item has none. hasData works for both shapes and answers yes or no:

ComponentSnippets.javaCompiles on Paper 26.3Compile Lab
Where this file livesregistries-data-components-snippetssrcmainjavacomexampleregistriesdatacomponentssnippetsComponentSnippets.java

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

  • registries-data-components-snippets/
    • src/main/java/com/example/registriesdatacomponentssnippets/Package com.example.registriesdatacomponentssnippets
      • ComponentSnippets.javayou are hereHelper class
      • KeySnippets.javaHelper class
      • RegistrySnippets.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.

41static Component readCustomNameOrFallback(ItemStack item) {42    return item.getDataOrDefault(DataComponentTypes.CUSTOM_NAME, Component.text("No name"));43}
  1. Returns the value, or the fallback you pass when the item has no value. It never returns null here.

Defaults: the item type already has components

Here is the part that surprises people. A brand new stack of apples has no custom name, but it still has components. It says max_stack_size is 64, that it is food, and that eating it takes 1.6 seconds. Where do those come from? From the prototype: every item type carries a default set of components, and every stack of that type starts with them.

A stack does not copy the whole prototype. It stores only your changes on top of it. When you ask for a component, Paper looks at the stack's changes first and falls back to the prototype.

Defaults from the item type, changes on the stack Every item type has default component values. A stack stores only its own changes. getData reads the change if there is one, otherwise the default. setData adds a change, unsetData removes the component, resetData deletes the change. Two layers: the item type's defaults, and this stack's changes Item type defaults same for every apple Changes on this stack what you did with the API What getData returns changes win, then defaults MAX_STACK_SIZE 64 setData(..., 16) overridden: 16 16 FOOD nutrition and saturation unsetData(FOOD) marked as removed null (not edible) CUSTOM_NAME no default resetData(...) change deleted null (no name) unsetData means "this item does not have it". resetData means "go back to what the item type says".
Three operations on an apple stack. The item type's defaults never change; each stack keeps its own list of edits.

You can read the defaults directly from the item type, without making any stack:

ComponentSnippets.javaCompiles on Paper 26.3Compile Lab
Where this file livesregistries-data-components-snippetssrcmainjavacomexampleregistriesdatacomponentssnippetsComponentSnippets.java

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

  • registries-data-components-snippets/
    • src/main/java/com/example/registriesdatacomponentssnippets/Package com.example.registriesdatacomponentssnippets
      • ComponentSnippets.javayou are hereHelper class
      • KeySnippets.javaHelper class
      • RegistrySnippets.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.

15static int defaultMaxStack(ItemType type) {16    return type.getDefaultData(DataComponentTypes.MAX_STACK_SIZE);17}
  1. The value this item type starts with. For ItemType.ENDER_PEARL and MAX_STACK_SIZE that is 16.
ComponentSnippets.javaCompiles on Paper 26.3Compile Lab
Where this file livesregistries-data-components-snippetssrcmainjavacomexampleregistriesdatacomponentssnippetsComponentSnippets.java

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

  • registries-data-components-snippets/
    • src/main/java/com/example/registriesdatacomponentssnippets/Package com.example.registriesdatacomponentssnippets
      • ComponentSnippets.javayou are hereHelper class
      • KeySnippets.javaHelper class
      • RegistrySnippets.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.

19static boolean isFoodByDefault(Material material) {20    return material.hasDefaultData(DataComponentTypes.FOOD);21}
  1. The same idea for a Material, and the right call for non-valued components that have no value to read.

Set, unset and reset

Because of the two layers, there are three different ways to change a component, and they do three different things. Beginners mix up the last two all the time.

CallWhat it doesResult for an apple's FOOD
setData(type, value)Stores your own value on the stack, hiding the default.Your food values win.
unsetData(type)Marks the component as removed, even if the item type normally has it.The apple is no longer food. getData returns null.
resetData(type)Deletes your change, so the item type's default shows again.The apple is food again with the normal values.
ComponentSnippets.javaCompiles on Paper 26.3Compile Lab
Where this file livesregistries-data-components-snippetssrcmainjavacomexampleregistriesdatacomponentssnippetsComponentSnippets.java

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

  • registries-data-components-snippets/
    • src/main/java/com/example/registriesdatacomponentssnippets/Package com.example.registriesdatacomponentssnippets
      • ComponentSnippets.javayou are hereHelper class
      • KeySnippets.javaHelper class
      • RegistrySnippets.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.

45static ItemStack stackOf99(ItemStack item) {46    item.setData(DataComponentTypes.MAX_STACK_SIZE, 99);47    return item;48}
  1. Overrides the default. This one stack now says "99".
ComponentSnippets.javaCompiles on Paper 26.3Compile Lab
Where this file livesregistries-data-components-snippetssrcmainjavacomexampleregistriesdatacomponentssnippetsComponentSnippets.java

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

  • registries-data-components-snippets/
    • src/main/java/com/example/registriesdatacomponentssnippets/Package com.example.registriesdatacomponentssnippets
      • ComponentSnippets.javayou are hereHelper class
      • KeySnippets.javaHelper class
      • RegistrySnippets.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.

50static ItemStack backToNormalStacking(ItemStack item) {51    item.resetData(DataComponentTypes.MAX_STACK_SIZE);52    return item;53}
  1. Forgets the override. Reading MAX_STACK_SIZE now gives the default again.
ComponentSnippets.javaCompiles on Paper 26.3Compile Lab
Where this file livesregistries-data-components-snippetssrcmainjavacomexampleregistriesdatacomponentssnippetsComponentSnippets.java

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

  • registries-data-components-snippets/
    • src/main/java/com/example/registriesdatacomponentssnippets/Package com.example.registriesdatacomponentssnippets
      • ComponentSnippets.javayou are hereHelper class
      • KeySnippets.javaHelper class
      • RegistrySnippets.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.

55static ItemStack removeFoodProperty(ItemStack apple) {56    apple.unsetData(DataComponentTypes.FOOD);57    return apple;58}
  1. Takes the component away completely. This is different from resetting: resetting would bring the default food back.

To find out whether a stack differs from its type, ask item.isDataOverridden(type). It is true when you set the component yourself and false when the value is just the default. And item.getDataTypes() returns every component type the stack currently has, defaults included.

Example plugin: /describe-item and /registry-peek

Time to see all of this on a real server. This plugin has two commands. /describe-item lists every component on the item in your hand and says whether each value was set on the stack or comes from the item type. /registry-peek looks a key up in three registries at once.

  • registries-data-components-demo/
    • src/
      • main/
        • java/
          • com/example/registriesdatacomponentsdemo/
            • RegistriesDemoPlugin.javaRegisters both commands when the server is ready
            • DescribeItemCommand.javaLists the components on the held item
            • RegistryPeekCommand.javaLooks a key up in the enchantment, item and block registries
        • resources/
          • plugin.ymlPlugin name, main class and the permission both commands use

Start with /describe-item. The interesting part is the loop over getDataTypes():

DescribeItemCommand.javaCompiles on Paper 26.3Compile Lab
Where this file livesregistries-data-components-demosrcmainjavacomexampleregistriesdatacomponentsdemoDescribeItemCommand.java

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

  • registries-data-components-demo/
    • src/main/
      • java/com/example/registriesdatacomponentsdemo/Package com.example.registriesdatacomponentsdemo
        • DescribeItemCommand.javayou are hereCommand (BasicCommand)
        • RegistriesDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • RegistryPeekCommand.javaCommand (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.

17@Override18public void execute(CommandSourceStack source, String[] args) {19    if (!(source.getExecutor() instanceof Player player)) {20        source.getSender().sendRichMessage("<red>Only players can use /describe-item.");21        return;22    }23    ItemStack item = player.getInventory().getItemInMainHand();24    if (item.isEmpty()) {25        player.sendRichMessage("<red>Hold an item in your main hand first.");26        return;27    }28 29    player.sendRichMessage("<gold>Components on <yellow><item></yellow>:",30        Placeholder.unparsed("item", item.getType().key().asString()));31 32    List<DataComponentType> types = item.getDataTypes().stream()33        .sorted(Comparator.comparing(type -> type.key().asString()))34        .toList();35    for (DataComponentType type : types) {36        String origin = item.isDataOverridden(type) ? "<green>set on this stack" : "<gray>default for the item type";37        player.sendRichMessage("<dark_gray>- <white><key> <dark_gray>= <aqua><value> <dark_gray>(" + origin + "<dark_gray>)",38            Placeholder.unparsed("key", type.key().asString()),39            Placeholder.unparsed("value", describeValue(item, type)));40    }41 42    for (DataComponentType type : item.getType().getDefaultDataTypes()) {43        if (!item.hasData(type)) {44            player.sendRichMessage("<dark_gray>- <red><key> <dark_gray>(removed with unsetData)",45                Placeholder.unparsed("key", type.key().asString()));46        }47    }48}
  1. Only a player holds items, so the console gets a polite refusal. The pattern variable player is ready to use if the check passes.
  2. Bare hands count as an empty stack, and there is nothing to describe.
  3. Every Material has a key too. asString() gives minecraft:apple.
  4. All component types the stack has right now, including the defaults it got from its item type.
  5. Sorts them by key, so the list reads in a steady alphabetical order.
  6. The question from the last section. true means you or a plugin changed it on this stack.
  7. Inserts the key as plain text. Keys can never smuggle in formatting tags this way.
  8. The components the item type normally has.
  9. If the type normally has it but this stack does not, someone called unsetData. The command points that out in red.

Every component has a different kind of value, so a helper turns a value into short text. It uses a switch that picks the first matching shape:

DescribeItemCommand.javaCompiles on Paper 26.3Compile Lab
Where this file livesregistries-data-components-demosrcmainjavacomexampleregistriesdatacomponentsdemoDescribeItemCommand.java

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

  • registries-data-components-demo/
    • src/main/
      • java/com/example/registriesdatacomponentsdemo/Package com.example.registriesdatacomponentsdemo
        • DescribeItemCommand.javayou are hereCommand (BasicCommand)
        • RegistriesDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • RegistryPeekCommand.javaCommand (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.

50private String describeValue(ItemStack item, DataComponentType type) {51    if (!(type instanceof DataComponentType.Valued<?> valued)) {52        return "(no value, just present)";53    }54    Object value = item.getData(valued);55    return switch (value) {56        case null -> "(none)";57        case Component component -> PlainTextComponentSerializer.plainText().serialize(component);58        case Number number -> number.toString();59        case Boolean bool -> bool.toString();60        case Enum<?> constant -> constant.name();61        case Key key -> key.asString();62        default -> "(complex value)";63    };64}
  1. A non-valued component has no value to read, so the helper stops early. When the check passes, valued is the same object seen as a valued type.
  2. Reads the value. It is typed as a plain Object because here the code does not know which component it is dealing with.
  3. Text values, such as the custom name, are turned into plain text.
  4. Many components, such as enchantments or attribute modifiers, hold complicated objects. This demo just says so instead of printing all of it.

Now hold an apple that has been edited with setData and unsetData, and run /describe-item. The list is long, so this shows a few lines of it:

Components on minecraft:apple:
- minecraft:custom_name = Lucky Apple (set on this stack)
- minecraft:max_stack_size = 16 (set on this stack)
- minecraft:rarity = COMMON (default for the item type)
- minecraft:repair_cost = 0 (default for the item type)
- minecraft:food (removed with unsetData)

The first two lines are your own changes, the next two are defaults the apple came with, and the last line shows the component that unsetData removed. Compare it with a plain apple: max_stack_size would say 64 and every line would say "default".

The second command shows registries in action. It reads one word from the player and asks three registries about it:

RegistryPeekCommand.javaCompiles on Paper 26.3Compile Lab
Where this file livesregistries-data-components-demosrcmainjavacomexampleregistriesdatacomponentsdemoRegistryPeekCommand.java

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

  • registries-data-components-demo/
    • src/main/
      • java/com/example/registriesdatacomponentsdemo/Package com.example.registriesdatacomponentsdemo
        • DescribeItemCommand.javaCommand (BasicCommand)
        • RegistriesDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • RegistryPeekCommand.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.

18@Override19public void execute(CommandSourceStack source, String[] args) {20    CommandSender sender = source.getSender();21    if (args.length == 0) {22        sendSizes(sender);23        return;24    }25    if (!Key.parseable(args[0])) {26        sender.sendRichMessage("<red>That is not a valid key. Try <white>minecraft:sharpness</white> or just <white>sharpness</white>.");27        return;28    }29    Key key = Key.key(args[0]);30    sender.sendRichMessage("<gold>Looking up <yellow><key></yellow>", Placeholder.unparsed("key", key.asString()));31 32    Enchantment enchantment = RegistryAccess.registryAccess().getRegistry(RegistryKey.ENCHANTMENT).get(key);33    if (enchantment != null) {34        sender.sendRichMessage("<gray>Enchantment registry: <green>found</green>, max level <white><level></white>",35            Placeholder.unparsed("level", String.valueOf(enchantment.getMaxLevel())));36    } else {37        sender.sendRichMessage("<gray>Enchantment registry: <red>no entry");38    }39 40    ItemType itemType = Registry.ITEM.get(key);41    if (itemType != null) {42        sender.sendRichMessage("<gray>Item registry: <green>found</green>, max stack size <white><size></white>",43            Placeholder.unparsed("size", String.valueOf(itemType.getMaxStackSize())));44    } else {45        sender.sendRichMessage("<gray>Item registry: <red>no entry");46    }47 48    BlockType blockType = Registry.BLOCK.get(key);49    if (blockType != null) {50        sender.sendRichMessage("<gray>Block registry: <green>found</green>, hardness <white><hardness></white>",51            Placeholder.unparsed("hardness", String.valueOf(blockType.getHardness())));52    } else {53        sender.sendRichMessage("<gray>Block registry: <red>no entry");54    }55}
  1. Never trust what players type. This stops an InvalidKeyException before it can happen.
  2. Turns sharpness or minecraft:sharpness into a key. Both mean the same thing.
  3. The enchantment registry, because it is data-driven and its Registry constant is deprecated.
  4. The item registry. The same key can exist in several registries, which is why minecraft:stone is both an item and a block.
  5. Each lookup can be null, so each one gets its own check.

The same class makes the tab completion list. suggest is a method of BasicCommand that Paper calls while the player is typing:

RegistryPeekCommand.javaCompiles on Paper 26.3Compile Lab
Where this file livesregistries-data-components-demosrcmainjavacomexampleregistriesdatacomponentsdemoRegistryPeekCommand.java

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

  • registries-data-components-demo/
    • src/main/
      • java/com/example/registriesdatacomponentsdemo/Package com.example.registriesdatacomponentsdemo
        • DescribeItemCommand.javaCommand (BasicCommand)
        • RegistriesDemoPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • RegistryPeekCommand.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.

66@Override67public Collection<String> suggest(CommandSourceStack source, String[] args) {68    String typed = args.length == 0 ? "" : args[args.length - 1];69    return Registry.ITEM.keyStream()70        .map(Key::asMinimalString)71        .filter(name -> name.startsWith(typed))72        .limit(40)73        .toList();74}
  1. The word the player is typing right now.
  2. Walks every item key, there are well over a thousand in Paper 26.3.
  3. Stops after 40 suggestions, so the list stays short.
In-game chat after /registry-peek
> /registry-peek sharpnessLooking up minecraft:sharpnessEnchantment registry: found, max level 5Item registry: no entryBlock registry: no entry> /registry-peek stoneLooking up minecraft:stoneEnchantment registry: no entryItem registry: found, max stack size 64Block registry: found, hardness 1.5

Finally, the plugin registers both commands in onEnable, in the same style you used for /kit on the Items page:

RegistriesDemoPlugin.javaCompiles on Paper 26.3Compile Lab
Where this file livesregistries-data-components-demosrcmainjavacomexampleregistriesdatacomponentsdemoRegistriesDemoPlugin.java

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

  • registries-data-components-demo/
    • src/main/
      • java/com/example/registriesdatacomponentsdemo/Package com.example.registriesdatacomponentsdemo
        • DescribeItemCommand.javaCommand (BasicCommand)
        • RegistriesDemoPlugin.javayou are hereMain class: Paper starts here (named as main in plugin.yml)
        • RegistryPeekCommand.javaCommand (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.

8@Override9public void onEnable() {10    getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, event -> {11        event.registrar().register("describe-item", "Lists every data component on your held item",12            new DescribeItemCommand());13        event.registrar().register("registry-peek", "Looks a key up in the enchantment, item and block registries",14            new RegistryPeekCommand());15    });16}
  1. Paper fires this lifecycle event when it is ready for commands.
  2. One command object per command. The text before it is the command's name.

Adding your own entry: a custom enchantment

Everything so far only read registries. Paper can also let a plugin add entries, but only during server startup, because the game reads many registries once and then locks them. The place for this is a bootstrapper, a class that runs before the worlds load. If you have not read about it yet, see Paper plugins, bootstrappers and loaders.

The example below adds an enchantment called Lifesteal. The bootstrapper registers it, and the plugin gives it a real effect: whoever hits something with a Lifesteal sword heals a little.

paper-plugin.ymlCompiles on Paper 26.3Compile Lab
Where this file livesregistries-data-components-enchantsrcmainresourcespaper-plugin.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.

  • registries-data-components-enchant/
    • src/main/
      • java/com/example/registriesdatacomponentsenchant/Package com.example.registriesdatacomponentsenchant
        • LifestealBootstrap.javaBootstrapper: runs before the server loads worlds
        • LifestealListener.javaListener: reacts to events
        • LifestealPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • LifestealSwordCommand.javaCommand (BasicCommand)
      • resources/Files copied into the jar as they are
        • paper-plugin.ymlyou are hereTells Paper about a Paper plugin: name, version, main class, bootstrapper
    • 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: LifestealEnchant2version: '1.0.0'3main: com.example.registriesdatacomponentsenchant.LifestealPlugin4bootstrapper: com.example.registriesdatacomponentsenchant.LifestealBootstrap5api-version: '26.3'6description: Adds a custom Lifesteal enchantment to the enchantment registry during bootstrap.7authors: [Guide]
  1. Names the class that Paper runs first. Without this line, the bootstrapper never runs.
  2. The normal plugin class. It runs later, when the server enables plugins.
LifestealBootstrap.javaCompiles on Paper 26.3Compile Lab
Where this file livesregistries-data-components-enchantsrcmainjavacomexampleregistriesdatacomponentsenchantLifestealBootstrap.java

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

  • registries-data-components-enchant/
    • src/main/
      • java/com/example/registriesdatacomponentsenchant/Package com.example.registriesdatacomponentsenchant
        • LifestealBootstrap.javayou are hereBootstrapper: runs before the server loads worlds
        • LifestealListener.javaListener: reacts to events
        • LifestealPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • LifestealSwordCommand.javaCommand (BasicCommand)
      • resources/Files copied into the jar as they are
        • paper-plugin.ymlTells Paper about a Paper plugin: name, version, main class, bootstrapper
    • 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.registriesdatacomponentsenchant;2 3import io.papermc.paper.plugin.bootstrap.BootstrapContext;4import io.papermc.paper.plugin.bootstrap.PluginBootstrap;5import io.papermc.paper.registry.data.EnchantmentRegistryEntry;6import io.papermc.paper.registry.event.RegistryEvents;7import io.papermc.paper.registry.keys.EnchantmentKeys;8import io.papermc.paper.registry.keys.tags.ItemTypeTagKeys;9import net.kyori.adventure.key.Key;10import net.kyori.adventure.text.Component;11import org.bukkit.inventory.EquipmentSlotGroup;12 13@SuppressWarnings("UnstableApiUsage")14public final class LifestealBootstrap implements PluginBootstrap {15 16    static final Key LIFESTEAL = Key.key("guide", "lifesteal");17 18    @Override19    public void bootstrap(BootstrapContext context) {20        context.getLifecycleManager().registerEventHandler(RegistryEvents.ENCHANTMENT.compose(), event ->21            event.registry().register(EnchantmentKeys.create(LIFESTEAL), builder -> builder22                .description(Component.text("Lifesteal"))23                .supportedItems(event.getOrCreateTag(ItemTypeTagKeys.SWORDS))24                .weight(2)25                .maxLevel(3)26                .minimumCost(EnchantmentRegistryEntry.EnchantmentCost.of(10, 8))27                .maximumCost(EnchantmentRegistryEntry.EnchantmentCost.of(30, 8))28                .anvilCost(4)29                .activeSlots(EquipmentSlotGroup.MAINHAND)));30    }31}
  1. Paper marks bootstrap APIs as experimental, and IntelliJ underlines them with a warning. This annotation tells IntelliJ "I know", so it stays quiet. The Java compiler does not care either way.
  2. The new enchantment's key. The namespace is guide, not minecraft, so it can never clash with vanilla.
  3. The event that fires after the enchantment registry has been filled with vanilla entries, and before it is locked. This is the moment you may add more.
  4. Adds one entry. The first argument is its key (a TypedKey, made by EnchantmentKeys.create). The second is a function that receives an empty builder for you to fill in.
  5. The name players see: "Lifesteal".
  6. Which items can get it. A tag is a named group of things; swords is the vanilla group of all swords.
  7. A rarity number: the higher it is, the more often the game picks this enchantment when it chooses at random. Whether the enchanting table may offer it at all is decided by tags, which this example does not set up, so the example plugin hands out the sword with a command.
  8. Lifesteal I to III.
  9. The enchanting table level range at which it appears: a base cost plus an amount per level.
  10. How much combining it in an anvil costs.
  11. The enchantment only counts while the item is held in the main hand.

The registry entry gives the enchantment a name, costs and rules. The game's own enchantments get their effects (extra damage, mining speed) from datapack files that this API does not generate, so a plugin enchantment needs a listener to do the work. Here it is:

LifestealListener.javaCompiles on Paper 26.3Compile Lab
Where this file livesregistries-data-components-enchantsrcmainjavacomexampleregistriesdatacomponentsenchantLifestealListener.java

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

  • registries-data-components-enchant/
    • src/main/
      • java/com/example/registriesdatacomponentsenchant/Package com.example.registriesdatacomponentsenchant
        • LifestealBootstrap.javaBootstrapper: runs before the server loads worlds
        • LifestealListener.javayou are hereListener: reacts to events
        • LifestealPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • LifestealSwordCommand.javaCommand (BasicCommand)
      • resources/Files copied into the jar as they are
        • paper-plugin.ymlTells Paper about a Paper plugin: name, version, main class, bootstrapper
    • 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.

18@EventHandler(ignoreCancelled = true)19public void onDamage(EntityDamageByEntityEvent event) {20    if (!(event.getDamager() instanceof Player attacker)) {21        return;22    }23    ItemStack weapon = attacker.getInventory().getItemInMainHand();24    int level = weapon.getEnchantmentLevel(lifesteal);25    if (level > 0) {26        attacker.heal(level);27    }28}
  1. Skip attacks that another plugin has already canceled, so a canceled hit never heals anyone.
  2. Only players use swords here. Arrows and mobs are ignored.
  3. 0 when the sword does not have the enchantment, otherwise its level.
  4. Heals 1 health point (half a heart) per level: 1 for Lifesteal I, 3 for Lifesteal III.

The main class checks that the bootstrapper really worked, and gives players a sword to test with:

LifestealPlugin.javaCompiles on Paper 26.3Compile Lab
Where this file livesregistries-data-components-enchantsrcmainjavacomexampleregistriesdatacomponentsenchantLifestealPlugin.java

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

  • registries-data-components-enchant/
    • src/main/
      • java/com/example/registriesdatacomponentsenchant/Package com.example.registriesdatacomponentsenchant
        • LifestealBootstrap.javaBootstrapper: runs before the server loads worlds
        • LifestealListener.javaListener: reacts to events
        • LifestealPlugin.javayou are hereMain class: Paper starts here (named as main in plugin.yml)
        • LifestealSwordCommand.javaCommand (BasicCommand)
      • resources/Files copied into the jar as they are
        • paper-plugin.ymlTells Paper about a Paper plugin: name, version, main class, bootstrapper
    • 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 onEnable() {13    Enchantment lifesteal = RegistryAccess.registryAccess()14        .getRegistry(RegistryKey.ENCHANTMENT)15        .get(LifestealBootstrap.LIFESTEAL);16    if (lifesteal == null) {17        getComponentLogger().error("The lifesteal enchantment was not registered. Disabling.");18        getServer().getPluginManager().disablePlugin(this);19        return;20    }21    getComponentLogger().info("Found {} with max level {}", lifesteal.key().asString(), lifesteal.getMaxLevel());22 23    getServer().getPluginManager().registerEvents(new LifestealListener(lifesteal), this);24    getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, event ->25        event.registrar().register("lifesteal-sword", "Gives you a sword with Lifesteal III",26            new LifestealSwordCommand(lifesteal)));27}
  1. An ordinary lookup, the same as any vanilla enchantment. The registry cannot tell yours from Mojang's.
  2. If the bootstrapper did not run, say so loudly and switch the plugin off instead of failing later with a confusing error.
LifestealSwordCommand.javaCompiles on Paper 26.3Compile Lab
Where this file livesregistries-data-components-enchantsrcmainjavacomexampleregistriesdatacomponentsenchantLifestealSwordCommand.java

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

  • registries-data-components-enchant/
    • src/main/
      • java/com/example/registriesdatacomponentsenchant/Package com.example.registriesdatacomponentsenchant
        • LifestealBootstrap.javaBootstrapper: runs before the server loads worlds
        • LifestealListener.javaListener: reacts to events
        • LifestealPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
        • LifestealSwordCommand.javayou are hereCommand (BasicCommand)
      • resources/Files copied into the jar as they are
        • paper-plugin.ymlTells Paper about a Paper plugin: name, version, main class, bootstrapper
    • 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.

20@Override21public void execute(CommandSourceStack source, String[] args) {22    if (!(source.getExecutor() instanceof Player player)) {23        source.getSender().sendRichMessage("<red>Only players can use /lifesteal-sword.");24        return;25    }26    ItemStack sword = ItemStack.of(Material.DIAMOND_SWORD);27    sword.setData(DataComponentTypes.ENCHANTMENTS, ItemEnchantments.itemEnchantments()28        .add(lifesteal, 3)29        .build());30    player.getInventory().addItem(sword);31    player.sendRichMessage("<green>Here is your Lifesteal III sword. Hit a mob and watch your hearts.");32}
  1. The component that stores enchantments. Its value type is ItemEnchantments.
  2. Starts a builder. Call add(enchantment, level) for each enchantment, then build().

When the server starts you see the plugin find its own enchantment:

Server console
[12:35:04 INFO]: [LifestealEnchant] Enabling LifestealEnchant v1.0.0[12:35:04 INFO]: [LifestealEnchant] Found guide:lifesteal with max level 3

With /describe-item in the same server, you can check the sword the command gives: the enchantments line shows that the stack carries its own enchantments. And /registry-peek guide:lifesteal reports Enchantment registry: found, max level 3.

Mistakes to avoid

  • Using a deprecated Registry constant. If the compiler warns about Registry.ENCHANTMENT, switch to RegistryAccess, as in the examples above.
  • Forgetting that get can return null. A typo in a key name gives null, not an error. Check it.
  • Mixing up unsetData and resetData. Unset removes the component, reset restores the default.
  • Calling getData on a non-valued component. It will not compile. Use hasData.
  • Trying to register a registry entry in onEnable. It is too late. Registry entries are added in a bootstrapper.
  • Capital letters in keys. guide:Lifesteal is invalid. Keys are lowercase.
Try it

A /bigstack toggle

Write a command /bigstack for the item in the player's main hand. The first time, it should raise the item's maximum stack size to 99. The second time, it should put the stack size back to normal and tell the player what the normal size is.

Refuse tools and armor (items that wear out), because they cannot stack.

Hint 1
The component is DataComponentTypes.MAX_STACK_SIZE. Which of setData, unsetData and resetData goes back to the default?
Hint 2
How can the command tell which of the two states the item is in? Look for a method that asks whether a component was changed from the default.
Hint 3
Items that wear out have the MAX_DAMAGE component. hasData can check for it.
Show the solution

The command asks isDataOverridden. If the stack size has been changed, it calls resetData and reads the default back with getData. Otherwise it calls setData with 99. Items with MAX_DAMAGE are refused first.

BigStackCommand.javaCompiles on Paper 26.3Compile Lab
Where this file livesregistries-data-components-exercisesrcmainjavacomexampleregistriesdatacomponentsexerciseBigStackCommand.java

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

  • registries-data-components-exercise/
    • src/main/
      • java/com/example/registriesdatacomponentsexercise/Package com.example.registriesdatacomponentsexercise
        • BigStackCommand.javayou are hereCommand (BasicCommand)
        • BigStackPlugin.javaMain class: Paper starts here (named as main in plugin.yml)
      • resources/Files copied into the jar as they are
        • plugin.ymlTells Paper the plugin's name, version and main class
    • 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.

14@Override15public void execute(CommandSourceStack source, String[] args) {16    if (!(source.getExecutor() instanceof Player player)) {17        source.getSender().sendRichMessage("<red>Only players can use /bigstack.");18        return;19    }20    ItemStack item = player.getInventory().getItemInMainHand();21    if (item.isEmpty()) {22        player.sendRichMessage("<red>Hold an item first.");23        return;24    }25    if (item.hasData(DataComponentTypes.MAX_DAMAGE)) {26        player.sendRichMessage("<red>Tools and armor wear out, so they cannot stack.");27        return;28    }29 30    if (item.isDataOverridden(DataComponentTypes.MAX_STACK_SIZE)) {31        item.resetData(DataComponentTypes.MAX_STACK_SIZE);32        player.sendRichMessage("<yellow>Back to normal: this item stacks to <size>.",33            Placeholder.unparsed("size", String.valueOf(item.getData(DataComponentTypes.MAX_STACK_SIZE))));34    } else {35        item.setData(DataComponentTypes.MAX_STACK_SIZE, BIG_STACK_SIZE);36        player.sendRichMessage("<green>This stack can now hold <size> items.",37            Placeholder.unparsed("size", String.valueOf(BIG_STACK_SIZE)));38    }39}
  1. Only items that wear out have a maximum damage.
  2. True when a plugin has already changed the stack size on this stack.
  3. Throws the override away. The default is back.

The plugin class that registers the command is in examples/plugins/registries-data-components-exercise in the download.

Recap

  • A key is namespace:value. Key, NamespacedKey and TypedKey are three classes for the same idea; Key.parseable checks text first.
  • A registry turns keys into objects. Use RegistryAccess with a RegistryKey for data-driven registries, and check for null after get.
  • ItemType and BlockType are the modern, separate types for items and blocks. Material still works and converts with asItemType().
  • An item is a type plus data components. Valued components carry a value, non-valued ones are on or off.
  • Every item type has default components. A stack stores only its changes: setData overrides, resetData restores the default, unsetData removes the component.
  • New registry entries, such as an enchantment, are added in a bootstrapper during startup.

Quick quiz

  1. What is the namespace of the key minecraft:diamond_sword?

  2. You call apple.unsetData(DataComponentTypes.FOOD). What does apple.hasData(DataComponentTypes.FOOD) return?

  3. Why is Registry.ENCHANTMENT deprecated?

  4. Which call correctly turns on the UNBREAKABLE component?

  5. Where must a plugin add a new enchantment to the registry?

Next steps