Skip to content

v5 ItemBuilder

Jake Moore edited this page Aug 31, 2026 · 6 revisions

ItemBuilder

⚠️ Usage ⚠️

Available in spigot-utils and its inheritors (spigot-jar).

ItemBuilder wraps a Bukkit ItemStack and lets you describe changes to it. Most of KamiCommon takes one: menus, icons, and the config loaders all speak ItemBuilder.

ItemStack stack = new ItemBuilder(XMaterial.DIAMOND_SWORD)
        .setAmount(1)
        .displayName(serializer.fromMiniMessage("<gold>Excalibur"))
        .lore(serializer.fromMiniMessage("<gray>Sharper than most"))
        .setEnchantment(XEnchantment.SHARPNESS, 5)
        .setUnbreakable(true)
        .build();

The prototype

v5 splits an ItemBuilder into a fixed prototype ItemStack and the changes you layer on top, a design borrowed from Paper's Data Component API. You always supply the prototype up front:

new ItemBuilder(itemStack)     // from an existing stack
new ItemBuilder(xMaterial)
new ItemBuilder(material)      // new in v5

Those are the only three constructors. build() clones the prototype, applies your changes, and returns a fresh stack. It never mutates what you passed in.

⚠️ The prototype reference is final, the ItemStack behind it is not. The constructor stores the stack you pass without copying it, and getPrototype() hands back that same instance rather than a copy. Anything done to it changes every item the builder produces from that point on. Treat it as read-only, and call getPrototype().clone() when you need something you can modify.

The material cannot change after construction. To switch it, copy your changes onto a new prototype:

ItemBuilder gold = iron.cloneWithNewPrototype(XMaterial.GOLDEN_SWORD);

Overloads exist for Material, XMaterial and ItemStack. clone() copies without changing the prototype.

You do not construct or inspect the change objects themselves. The vocabulary is the setters:

you want to call
set a value setEnchantment(...), addItemFlag(...), setAmount(...)
clear your change and let the prototype show through resetEnchantment(...), resetItemFlag(...), resetDamage()
actively suppress a value the prototype has removeEnchantment(...), removeItemFlag(...)

reset and remove are different. reset forgets that you said anything; remove says "not this one", even if the prototype had it.

Glow

addGlow() sets the glow patch, removeGlow() clears it, and setGlow(boolean) is the two as one call. disableGlow() is an alias of removeGlow().

⚠️ setGlow(false) clears the patch, it does not force "no glow". There is no patch value meaning "suppress the prototype's glint", so an item whose prototype already carries enchantments still glints afterwards.

hasGlow() reports the effective value. With a patch set it returns the patch; with no patch it falls through to the prototype, which glints exactly when it carries at least one enchantment. isAddGlow() is an alias of it.

Loading from config

ItemBuilder icon = ItemBuilder.load(section);
ItemBuilder icon = ItemBuilder.load(xMaterial, section);   // material fixed in code
ItemBuilder icon = ItemBuilderLoader.loadPatches(prototype, section);   // ignore material keys

or straight off a config section:

ItemBuilder icon = config.parseItemBuilder("myItem");

Config keys

key type default notes
material / type String none single-item form. material wins if both are present
materials / types list of String none menu icons only, see below
data int 0 legacy pre-1.13 variant, e.g. wool colour
amount int item default
damage int none durability damage; not the same as data
name String none format auto-detected
lore list of String none format auto-detected per line
unbreakable boolean none
item-flags list of String none XItemFlag names, case and space tolerant
enchantments section none key is the enchantment name, value the level
glow boolean false addGlow is an accepted alias
skull-owner String none
hide-attributes boolean nothing hidden a global default applies on the menu path, see below
excalibur:
  material: DIAMOND_SWORD
  name: "&6Excalibur"
  lore:
    - "&7Sharper than most"
  amount: 1
  damage: 50
  enchantments:
    SHARPNESS: 5
    UNBREAKING: 3
  item-flags:
    - HIDE_ENCHANTS

Names and lore go through format auto-detection. See Text and Components.

hide-attributes behaves differently depending on how the section is loaded. Through ItemBuilder.load(...) or parseItemBuilder(...), an absent key means nothing is hidden. Through MenuIconLoader, that is, when the section is a menu icon, an absent key falls back to MenuIconLoader.Config.isHideIconAttributes(), which defaults to true.

The v4 spelling hideAttributes is no longer read on either path.

Cycling materials

The list forms are read by MenuIconLoader only, so they work on a menu icon and not through ItemBuilder.load(...) or parseItemBuilder(...). A section defining only materials: throws IllegalArgumentException when loaded as a plain item.

Giving a list makes the icon rotate between materials. Accepted keys, in precedence order: materials, material as a list, types, type as a list.

An invalid material name in a list is dropped with a warning naming the config path, where the single form throws IllegalArgumentException.

Migrating from v4

Constructors: twelve became three

v4 v5
new ItemBuilder(section) ItemBuilder.load(section)
new ItemBuilder(section, offlinePlayer) ItemBuilder.load(section).setSkullOwner(player.getName())
new ItemBuilder(xMat, section) ItemBuilder.load(xMat, section)
new ItemBuilder(xMat, section, offlinePlayer) ItemBuilder.load(xMat, section).setSkullOwner(...)
new ItemBuilder(base, section) ItemBuilder.load(base, section) or ItemBuilderLoader.loadPatches(base, section)
new ItemBuilder(xMat, (short) dmg) new ItemBuilder(xMat).setDamage(dmg)
new ItemBuilder(xMat, amount) new ItemBuilder(xMat).setAmount(amount)
new ItemBuilder(xMat, amount, (short) dmg) new ItemBuilder(xMat).setAmount(amount).setDamage(dmg)
new ItemBuilder(stack, false) new ItemBuilder(stack)
new ItemBuilder(stack, true) new ItemBuilder(stack.clone())
new ItemBuilder(stack) new ItemBuilder(stack.clone()) to keep v4's cloning behaviour
new IAItemBuilder("ns:id") new ItemBuilder(CustomStack.getInstance("ns:id").getItemStack())
any new IBuilder(...) none. IBuilder is now an interface, not an abstract class

Methods

v4 v5
getBase() getPrototype()
setBase(ItemStack) cloneWithNewPrototype(ItemStack)
setType / setMaterial(XMaterial) / setMaterial(Material) cloneWithNewPrototype(...)
addEnchant(XEnchantment, int) setEnchantment(XEnchantment, int)
addEnchantments(Map) setEnchantments(Map)
addFlag(ItemFlag) addItemFlag(XItemFlag)
addGlow(boolean) setGlow(boolean)
getUnbreakable()TriState isUnbreakable()boolean
loadTypes(ConfigurationSection) ItemTypeLoader.loadType(section)
parseMaterial / parseXMaterial ItemTypeLoader.loadTypeByString(String, Integer)
loadClone(IBuilder) clone() or cloneWithNewPrototype(...)
build(Player, int) none. Material cycling moved to MenuIcon
getNbtData() none
ConfigurationSection#setItemBuilder(key, b) setItemStack(key, b.build()), or put(key, b)
ConfigurationSection#getItemBuilder(key) parseItemBuilder(key), which is not a drop-in. See below
StringUtilP.p(player, s) SoftPlaceholderAPI.setPlaceholders(player, s)

⚠️ Two methods kept their name and changed their type, so they break on recompile without an obvious error message: getDamage() went shortint, and getItemFlags() went List<ItemFlag>Set<XItemFlag>.

⚠️ getItemBuilder and parseItemBuilder are different operations. The old one read a serialized ItemStack at that key and returned null if absent. The new one reads a YAML subsection and throws IllegalArgumentException if no valid material is defined.

Behaviour changes with no signature change

  • setUnbreakable(true) no longer adds HIDE_UNBREAKABLE for you. Add it yourself if you want it.
  • hideAttributes() now names HIDE_ADDITIONAL_TOOLTIP instead of HIDE_POTION_EFFECTS. XSeries maps these to the same flag, so behaviour is unchanged.
  • NBT support was removed. getNbtData() and the nbt: config section are gone. An nbt: block in an existing config is ignored, with a warning naming the config path. Apply NBT after build() using the bundled NBT-API.
  • Material-load failure throws IllegalArgumentException, where v4 threw IllegalStateException. Update any catch block that named the old type.

Clone this wiki locally