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.
// 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" );
Item and Component
NBT Tag
// ItemStack
Tag < ItemStack > itemTag = Tag . ItemStack ( "heldItem" );
// Text Component
Tag < Component > textTag = Tag . Component ( "displayName" );
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(key, serializer)
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);
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 ));
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
Sets a tag value (null to remove) Tag < Integer > scoreTag = Tag . Integer ( "score" );
entity . setTag (scoreTag, 100 );
// Remove tag
entity . setTag (scoreTag, null );
Gets a tag value (returns null if not set) Integer score = entity . getTag (scoreTag);
if (score != null ) {
// Tag exists
}
Checks if a tag exists if ( entity . hasTag (scoreTag)) {
int score = entity . getTag (scoreTag);
}
Removes a tag entity . removeTag (scoreTag);
Advanced Operations
Sets a new value and returns the previous value Integer oldScore = entity . getAndSetTag (scoreTag, 150 );
if (oldScore != null ) {
player . sendMessage ( "Previous score: " + oldScore);
}
Updates a tag value using an operator function // Increment score
entity . updateTag (scoreTag, score -> score == null ? 1 : score + 1 );
updateAndGetTag(tag, operator)
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)
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
Creates a deep copy of the handler TagHandler original = entity . tagHandler ();
TagHandler copy = original . copy ();
Creates a read-only copy (more efficient than full copy) TagReadable snapshot = handler . readableCopy ();
String name = snapshot . getTag ( Tag . String ( "name" ));
Converts handler contents to NBT compound CompoundBinaryTag nbt = handler . asCompound ();
// Save to file, send over network, etc.
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
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
Provides a default value via supplier (lazy evaluation) Tag < Long > spawnTimeTag = Tag . Long ( "spawnTime" )
. defaultValue (System :: currentTimeMillis);
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" );
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
Player Stats System
Custom Item Data
Cooldown System
Quest System
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 );