Skip to main content
Minestom’s tag system provides a flexible way to store custom data on entities, blocks, items, and other game objects. Tags are type-safe, serializable to NBT, and support complex data structures.

Overview

Tags are key-value pairs where:
  • Keys are strings that identify the data
  • Values are type-safe and can be primitives, collections, or custom objects
  • All tags are automatically serialized to NBT format
Tags are the recommended way to store custom data in Minestom. They integrate seamlessly with Minecraft’s NBT system and are automatically persisted.

Tag Types

Minestom provides built-in tag types for common data types.

Primitive Tags

// Integer tags
Tag<Byte> byteTag = Tag.Byte("myByte");
Tag<Short> shortTag = Tag.Short("myShort");
Tag<Integer> intTag = Tag.Integer("myInt");
Tag<Long> longTag = Tag.Long("myLong");

// Floating point tags
Tag<Float> floatTag = Tag.Float("myFloat");
Tag<Double> doubleTag = Tag.Double("myDouble");

Complex Tags

// ItemStack
Tag<ItemStack> itemTag = Tag.ItemStack("heldItem");

// Text Component
Tag<Component> textTag = Tag.Component("displayName");

Collection Tags

list()
Tag<List<T>>
Creates a list tag from any existing tag type
Tag<List<String>> namesTag = Tag.String("name").list();

// Usage
entity.setTag(namesTag, List.of("Alice", "Bob", "Charlie"));
List<String> names = entity.getTag(namesTag);

Structure Tags

Structure(key, serializer)
Tag<T>
Creates a tag for complex custom objects using a serializer
record PlayerStats(int kills, int deaths, double kdr) {}

TagSerializer<PlayerStats> serializer = TagSerializer.of(
    tag -> new PlayerStats(
        tag.getTag(Tag.Integer("kills")),
        tag.getTag(Tag.Integer("deaths")),
        tag.getTag(Tag.Double("kdr"))
    ),
    (tag, stats) -> {
        tag.setTag(Tag.Integer("kills"), stats.kills());
        tag.setTag(Tag.Integer("deaths"), stats.deaths());
        tag.setTag(Tag.Double("kdr"), stats.kdr());
    }
);

Tag<PlayerStats> statsTag = Tag.Structure("stats", serializer);
Structure(key, Class)
Tag<T>
Experimental: Creates a tag for record types with automatic serialization
record Position(double x, double y, double z) {}

Tag<Position> posTag = Tag.Structure("position", Position.class);
entity.setTag(posTag, new Position(10.5, 64.0, 20.5));

Transient Tags

Transient(key)
Tag<T>
Creates a tag that is NOT serialized to NBT (server-side only)
Tag<Long> lastActionTime = Tag.Transient("lastAction");
entity.setTag(lastActionTime, System.currentTimeMillis());
// This tag will not be saved to disk or sent to clients

Taggable Interface

Objects that can store tags implement the Taggable interface.

Common Taggable Objects

  • Entities (players, mobs, projectiles)
  • ItemStacks
  • Blocks (via block handlers)
  • Instances (worlds)
  • Any custom object implementing Taggable

Basic Operations

setTag(tag, value)
void
Sets a tag value (null to remove)
Tag<Integer> scoreTag = Tag.Integer("score");
entity.setTag(scoreTag, 100);

// Remove tag
entity.setTag(scoreTag, null);
getTag(tag)
T
Gets a tag value (returns null if not set)
Integer score = entity.getTag(scoreTag);
if (score != null) {
    // Tag exists
}
hasTag(tag)
boolean
Checks if a tag exists
if (entity.hasTag(scoreTag)) {
    int score = entity.getTag(scoreTag);
}
removeTag(tag)
void
Removes a tag
entity.removeTag(scoreTag);

Advanced Operations

getAndSetTag(tag, value)
T
Sets a new value and returns the previous value
Integer oldScore = entity.getAndSetTag(scoreTag, 150);
if (oldScore != null) {
    player.sendMessage("Previous score: " + oldScore);
}
updateTag(tag, operator)
void
Updates a tag value using an operator function
// Increment score
entity.updateTag(scoreTag, score -> score == null ? 1 : score + 1);
updateAndGetTag(tag, operator)
T
Updates tag and returns the new value
int newScore = entity.updateAndGetTag(scoreTag, s -> s == null ? 0 : s + 10);
player.sendMessage("New score: " + newScore);
getAndUpdateTag(tag, operator)
T
Returns old value and then updates tag
int oldScore = entity.getAndUpdateTag(scoreTag, s -> s == null ? 0 : s + 10);

TagHandler

The TagHandler class manages tag storage and serialization. All Taggable objects have a TagHandler.

Creating Tag Handlers

// Create empty handler
TagHandler handler = TagHandler.newHandler();

// Use it
handler.setTag(Tag.String("name"), "Steve");
String name = handler.getTag(Tag.String("name"));

Handler Operations

copy()
TagHandler
Creates a deep copy of the handler
TagHandler original = entity.tagHandler();
TagHandler copy = original.copy();
readableCopy()
TagReadable
Creates a read-only copy (more efficient than full copy)
TagReadable snapshot = handler.readableCopy();
String name = snapshot.getTag(Tag.String("name"));
asCompound()
CompoundBinaryTag
Converts handler contents to NBT compound
CompoundBinaryTag nbt = handler.asCompound();
// Save to file, send over network, etc.
updateContent(compound)
void
Replaces all handler content with NBT data
// Clear all tags
handler.updateContent(CompoundBinaryTag.empty());

// Load new data
handler.updateContent(loadedNbt);

Tag Modifiers

Tags can be modified with various options.

Default Values

defaultValue(value)
Tag<T>
Provides a default value when tag is not set
Tag<Integer> healthTag = Tag.Integer("health").defaultValue(100);

int health = entity.getTag(healthTag); // Returns 100 if not set
defaultValue(supplier)
Tag<T>
Provides a default value via supplier (lazy evaluation)
Tag<Long> spawnTimeTag = Tag.Long("spawnTime")
    .defaultValue(System::currentTimeMillis);

Path Tags

path(path...)
Tag<T>
Creates a nested tag path
Tag<String> nameTag = Tag.String("value").path("player", "info", "name");
// Equivalent to NBT: {player: {info: {name: "Steve"}}}

entity.setTag(nameTag, "Steve");

Mapped Tags

map(readMap, writeMap)
Tag<R>
Transforms tag values during read/write
// Store milliseconds as seconds
Tag<Integer> secondsTag = Tag.Long("time")
    .map(
        millis -> (int)(millis / 1000),  // Read: ms -> seconds
        seconds -> seconds * 1000L        // Write: seconds -> ms
    );

entity.setTag(secondsTag, 30); // Stores 30000ms
int seconds = entity.getTag(secondsTag); // Returns 30

Practical Examples

public class PlayerStatsManager {
    private static final Tag<Integer> KILLS = Tag.Integer("kills").defaultValue(0);
    private static final Tag<Integer> DEATHS = Tag.Integer("deaths").defaultValue(0);
    private static final Tag<Double> KDR = Tag.Double("kdr").defaultValue(0.0);
    
    public static void addKill(Player player) {
        player.updateTag(KILLS, k -> k + 1);
        updateKDR(player);
    }
    
    public static void addDeath(Player player) {
        player.updateTag(DEATHS, d -> d + 1);
        updateKDR(player);
    }
    
    private static void updateKDR(Player player) {
        int kills = player.getTag(KILLS);
        int deaths = player.getTag(DEATHS);
        double kdr = deaths == 0 ? kills : (double) kills / deaths;
        player.setTag(KDR, kdr);
    }
    
    public static void displayStats(Player player) {
        player.sendMessage(Component.text("Stats:"));
        player.sendMessage(Component.text("Kills: " + player.getTag(KILLS)));
        player.sendMessage(Component.text("Deaths: " + player.getTag(DEATHS)));
        player.sendMessage(Component.text("K/D: " + 
            String.format("%.2f", player.getTag(KDR))));
    }
}

Best Practices

Use static final Tag instances: Create tag instances once and reuse them throughout your code. This improves performance and ensures consistency.
// Good
private static final Tag<Integer> SCORE = Tag.Integer("score");

// Bad - creates new tag instance every time
public int getScore(Player player) {
    return player.getTag(Tag.Integer("score"));
}
Use transient tags for temporary data: Data that doesn’t need to persist (like cooldowns, cached calculations) should use Tag.Transient() to avoid unnecessary serialization.
Check for null values: getTag() returns null if the tag is not set. Always handle null values or use defaultValue().
// With null check
Integer score = player.getTag(SCORE);
if (score != null) {
    // Use score
}

// With default value (preferred)
Tag<Integer> SCORE = Tag.Integer("score").defaultValue(0);
int score = player.getTag(SCORE); // Never null
Use updateTag for modifications: Instead of get-modify-set, use updateTag() for atomic updates.
// Good
player.updateTag(SCORE, s -> s + 10);

// Less ideal
int score = player.getTag(SCORE);
player.setTag(SCORE, score + 10);

Build docs developers (and LLMs) love