// Creating tags
Tag<String> nameTag = Tag.String("custom_name");
Tag<Integer> levelTag = Tag.Integer("player_level");
Tag<Double> balanceTag = Tag.Double("player_balance");
Tag<Boolean> isVipTag = Tag.Boolean("is_vip");

// Setting tags (on objects that implement TagWritable)
Player player = // ... get player
if (player instanceof TagWritable writable) {
    writable.setTag(nameTag, "CustomPlayer");
    writable.setTag(levelTag, 50);
    writable.setTag(balanceTag, 1000.0);
    writable.setTag(isVipTag, true);
}

// Reading tags (on objects that implement TagReadable)
if (player instanceof TagReadable readable) {
    String name = readable.getTag(nameTag); // May return null
    Integer level = readable.getTag(levelTag);
    Double balance = readable.getTag(balanceTag);
    Boolean isVip = readable.getTag(isVipTag);
}

// Checking if tag exists
if (player instanceof TagReadable readable) {
    if (readable.hasTag(levelTag)) {
        // Tag exists
    }
}

// Removing tags
if (player instanceof TagWritable writable) {
    writable.removeTag(levelTag);
}

// Using tags in custom entity class
public class CustomEntity extends EntityLiving {
    private static final Tag<String> CUSTOM_DATA = Tag.String("custom_data");
    private static final Tag<Integer> CUSTOM_LEVEL = Tag.Integer("custom_level");
    
    public void setCustomData(String data) {
        setTag(CUSTOM_DATA, data);
    }
    
    public String getCustomData() {
        return getTag(CUSTOM_DATA);
    }
    
    public void setCustomLevel(int level) {
        setTag(CUSTOM_LEVEL, level);
    }
    
    public int getCustomLevel() {
        Integer level = getTag(CUSTOM_LEVEL);
        return level != null ? level : 0;
    }
}

// Tags persist across server restarts (if entity is saved)
// Tags are automatically serialized with entity data
// Tags use efficient NBT format for storage