Skip to content

v5 Text and Components

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

Text and Components

⚠️ Usage ⚠️

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

v5 replaced coloured Strings with VersionedComponent, a component type that works identically on every supported server from 1.8.8 to 26.2. Most of KamiCommon now takes and returns it, and the String overloads on ItemBuilder are deprecated as of 5.0.0-alpha.26.

You never name an Adventure type, and you never write a version check.

The entry point

VersionedComponentSerializer s = NmsAPI.getVersionedComponentSerializer();

It is a factory, not a holder. Six ways in:

s.fromMiniMessage("<gold>Hello");                       // MiniMessage
s.fromMiniMessage("<gold>Hi <player>", placeholders);   // MiniMessage + TextPlaceholder...
s.fromLegacyAmpersand("&6Hello");                       // &6 codes
s.fromLegacySection("§6Hello");                         // §6 codes
s.fromPlainText("Hello");                               // no formatting at all
s.fromJson(json);                                       // Minecraft component JSON

What you can do with a component

Every builder method returns a new instance. Nothing mutates.

VersionedComponent msg = s.fromMiniMessage("<yellow>[Claim]")
        .click(ClickAction.RUN_COMMAND, "claim daily")   // no leading slash
        .hover(s.fromMiniMessage("<green>Click to claim"))
        .decorate(TextDecoration.ITALIC, false)
        .append(s.fromPlainText(" today"));

msg.sendTo(player);                  // also sendTo(CommandSender...) and sendTo(Collection<CommandSender>)

Five ways out:

msg.serializeMiniMessage();
msg.serializeLegacyAmpersand();
msg.serializeLegacySection();
msg.serializePlainText();
msg.serializeJson();                 // use this to store or transport a component

serializeJson() and fromJson(String) are the pair to use for persistence or for sending a component between services. plainText() is deprecated in favour of serializePlainText().

Item hovers

hoverItem(ItemStack) is the item form of hover(VersionedComponent). The hover shows the item with its name, lore and enchantments as a player would see them in an inventory. A component carries one hover, so the two methods replace each other.

VersionedComponent line = s.fromMiniMessage("<yellow>[Kit icon]").hoverItem(icon);

Air throws IllegalArgumentException, because there is no item to show.

⚠️ On 1.8 through 1.12 the hover loses the item's Damage value. An item hover carries an id, a count and a tag compound, and on those versions a variant or a durability lives in a top-level Damage field, outside tag, with nowhere to go. Red wool therefore hovers as white wool and a worn pickaxe as an undamaged one. Name, lore and enchantments are inside tag and survive. From 1.13 the variant is part of the id and durability moved into tag, so nothing is lost. This is the shape of an item hover itself, not a limit of this library.

Placeholders, and the one that is safe

TextPlaceholder fills a <tag> in a MiniMessage string. There are three kinds and the difference matters:

TextPlaceholder.literal("player", player.getName());     // inserted verbatim, MiniMessage NOT parsed
TextPlaceholder.miniMessage("rank", "<red><bold>ADMIN"); // value is parsed as MiniMessage
TextPlaceholder.component("item", someComponent);        // value is an existing component

The key is the tag name without angle brackets, so literal("player", ...) fills <player>.

⚠️ Use literal(...) for anything a user controls, such as names, nicknames, chat input and faction names. The other two parse their value, so a player called <red>x</red> could inject formatting. This is the only escape-safe route.

VersionedComponent msg = s.fromMiniMessage(
        "<gray>Welcome back, <gold><player></gold>!",
        TextPlaceholder.literal("player", player.getName()));

Clicks and decorations

ClickAction: RUN_COMMAND, SUGGEST_COMMAND, OPEN_URL, COPY_TO_CLIPBOARD. Command values carry no leading slash.

COPY_TO_CLIPBOARD requires 1.16 or newer. Below that it throws UnsupportedOperationException naming the server version, rather than silently dropping the click.

Hovers and clicks reach the client on every supported version. Below 1.18.2 a component is serialized and handed to the bungee-chat the server itself ships, which reads the hoverEvent and clickEvent key names, so those are the names KamiCommon emits on those tiers.

TextDecoration: BOLD, ITALIC, UNDERLINED, STRIKETHROUGH, OBFUSCATED.

Minecraft italicises custom item names and lore by default. KamiCommon suppresses that for you, so an item name written through VersionedComponentUtil renders upright on every supported version without any decoration call. Ask for italics explicitly if you want them:

s.fromMiniMessage("<light_purple>Daily Crate").decorate(TextDecoration.ITALIC, true);

Only the unset case is suppressed, so an explicit true is honoured and an explicit false is harmless.

Item names and lore

VersionedComponentUtil writes components onto an ItemMeta on any version:

ItemMeta meta = stack.getItemMeta();
VersionedComponentUtil.setDisplayName(meta, s.fromMiniMessage("<gold>Excavator"));
VersionedComponentUtil.setLore(meta, List.of(
        s.fromMiniMessage("<gray>Right-click to dig")));
VersionedComponentUtil.addLoreLine(meta, s.fromMiniMessage("<dark_gray>Legendary"));
stack.setItemMeta(meta);

getDisplayName(meta) and getLore(meta) read them back, returning null when unset.

ItemBuilder takes components directly, which is usually easier:

new ItemBuilder(XMaterial.DIAMOND_PICKAXE)
        .displayName(s.fromMiniMessage("<gold>Excavator"))
        .lore(s.fromMiniMessage("<gray>Right-click to dig"))
        .build();

Multi-line messages: MiniMessageBuilder

For chat messages, especially ones read from config, MiniMessageBuilder handles multiple lines, replacements and PlaceholderAPI in one object.

MiniMessageBuilder.fromMiniMessage(config, "messages.welcome")
        .replace("{player}", player.getName())
        .send(player);

Four factory families, each with five overloads (a single String, a Collection<String>, a String..., and a Bukkit or KamiCommon ConfigurationSection plus a key):

  • fromMiniMessage(...)
  • fromLegacyAmpersand(...)
  • fromLegacySection(...)
  • fromStringParser(...), which detects the format, see below

A config key may hold a String or a List<String>; a list becomes one line per entry. Anything else throws IllegalArgumentException.

PlaceholderAPI translation is on by default; turn it off with setTranslatePAPI(false).

⚠️ replace(String, String) serializes to MiniMessage, replaces, and re-parses, so a replacement containing MiniMessage syntax will be parsed. PAPI translation works the same way. For user-controlled values use TextPlaceholder.literal(...) on the serializer instead.

MessageBuilder still exists and is not deprecated. It is the String-based sibling. Use MiniMessageBuilder for new code.

Format detection for config strings

Menu titles, icon names, every lore line and subsystem message prefixes are read through ColoredStringParser.parse(String), which picks a format from the content, in this order:

  1. contains a § section symbol → legacy section codes
  2. contains a legacy colour code, meaning & followed by a code character → legacy ampersand codes
  3. contains a <tag> → MiniMessage
  4. otherwise → plain text

So &7Usage: &f/kit <name> is read as legacy and keeps its colours, while <gray>Tips & Tricks is read as MiniMessage, because a bare & in prose is not a colour code. You cannot mix the two formats in one string; pick one per line.

Console logging

ComponentLogger writes components to console. KamiPlugin already builds one:

getColorComponentLogger().info(s.fromMiniMessage("<green>Loaded <count> arenas",
        TextPlaceholder.literal("count", String.valueOf(arenas.size()))));

info, warn, warning, severe and error each take a component, or a Throwable and a component. debug takes a component only. setMessagePrefix(VersionedComponent) prefixes every line. The String-taking overrides are deprecated in favour of the component forms, as is KamiPlugin's old string logger getter.

See also

Clone this wiki locally