Skip to content
LoneDev6Public

Repository files navigation



Dependency

FastNbt-jar

Comparison to NBT API

Installations

Lazy item editing

For explicit control, use a single-use editor:

try (NItemEditor editor = NItem.edit(item))
{
    editor.getOrAddCompound("itemsadder").setInt("usages", 25);
    editor.setCustomNameJson("{\"text\":\"Ruby sword\"}");
    editor.save();
}

save() applies changes and closes the editor; a second save or further access throws. close() is idempotent and discards unsaved changes, including on an exception before save. An exception after a successful save cannot undo that save. Read-only save does not apply metadata. Keep the scope short and do not change the original item while it is open. If application itself fails, the editor closes; rollback of a partially failed platform commit is not guaranteed.

The callback form manages saving and closing automatically (calling save() or close() inside it throws, preserving rollback when the callback fails):

NItem.edit(item, editor ->
{
    NCompound data = editor.getOrAddCompound("itemsadder");
    data.setInt("usages", 25);
    data.setInt("max_usages", 25);
    editor.setCustomNameJson("{\"text\":\"Ruby sword\"}");
    editor.setLoreJson(List.of("{\"text\":\"25 uses\"}"));
}); // One metadata commit; no save() required.

The editor opens without converting the item. Reads resolve data on demand; the first mutation creates an isolated draft. A read-only callback does not commit. If the callback throws, the draft is discarded, including name/lore changes, on both Bukkit and Craft items. An attempted setter counts as a mutation even when its value is unchanged.

Compound and list views belong to the callback and throw after it returns. A view also throws if its entry is replaced, removed or shifted. Array getters and raw-tag access return detached snapshots; changing them does not write back. Use setters to apply changes. Inserted compounds, lists and arrays are copied. This differs deliberately from ordinary NCompound/NList live aliases. Run on the owning server thread and do not mutate or recursively edit the original item during the callback.

The initial editor supports custom NBT and JSON custom name, item name and lore; material and amount remain outside this transaction. Existing NItem setters and save() keep their behavior. Consumers such as ItemsAdder can adopt editing selectively; they must first load a FastNBT build containing this API (including shaded copies in NativePackets).

Batched custom data

NItem.modifyCustomData(item, tag ->
{
    tag.setString("id", "example:sword");
    tag.setInt("level", 3);
    tag.getOrAddCompound("stats").setInt("damage", 12);
});

The batch commits once, including for ordinary Bukkit items; no save() is needed. On 1.20.5+ it edits only custom data and preserves other components. Older servers use the legacy item tag. The callback works on a detached compound: an exception does not commit, and retained compounds, lists or arrays do not alter the committed item later. Use it on the owning server thread and do not modify the item itself inside the callback. Existing NItem setters keep their immediate/Craft versus explicit save()/Bukkit behavior.

Batch/conversion regressions run on an isolated local server:

rtk ./gradlew --no-daemon build
rtk proxy python3 adapters/tests/run-custom-data.py 26.2 \
  --server /path/to/paper.jar --java /path/to/java --platform paper \
  --bootstrap-cache /path/to/matching-mojang.jar \
  --evidence-root /path/to/verified-scratch/task/semantic

The probe checks ordinary and batched writes, callback rollback, clone/view/array isolation, nested replacement and preservation of item metadata. The runner stops its own server and removes the disposable world.

Usability

FastNbt is easier to use compared to NBT API and requires less boilerplate code.

Creating a head texture

FastNBT writes SkullOwner NBT or the native profile Data Component automatically.

NItem nItem = new NItem(new ItemStack(Material.PLAYER_HEAD));
nItem.setSkull("dummy", "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYjc4ZWYyZTRjZjJjNDFhMmQxNGJmZGU5Y2FmZjEwMjE5ZjViMWJmNWIzNWE0OWViNTFjNjQ2Nzg4MmNiNWYwIn19fQ==");

Renaming an item

FastNBT accepts JSON text components for item names. Use the Bukkit API as usual for legacy strings.

NItem nItem = new NItem(new ItemStack(Material.STONE));
nItem.setCustomNameJson("{\"text\":\"Example Name\",\"color\":\"blue\"}");

Setting an attribute modifier

FastNBT writes legacy NBT or modern Data Components automatically, depending on the server version.

NItem nItem = new NItem(new ItemStack(Material.DIAMOND_BOOTS));
nItem.setAttributeModifier(
        AttributeName.MOVEMENT_SPEED,
        AttributeOperation.ADD,
        0.1,
        "movement_speed",
        "feet",
        1337,
        1337
);

Operations are ADD, MULTIPLY_BASE, and MULTIPLY_TOTAL. AttributeName contains every vanilla attribute and resolves its exact NMS name for the current Minecraft version. Legacy names such as attackDamage, generic.attackDamage, and horse.jumpStrength are accepted and converted automatically. Using an attribute before the version that introduced it throws IllegalArgumentException. The existing String and integer-operation overload remains available for compatibility.

Setting an attribute modifier manually (Minecraft 1.20.4 and older)

Use the high-level method above for multi-version code. For direct legacy NBT access:

UUID uuid = new UUID(1337, 1337);
NCompound attribute = nItem.getOrAddList("AttributeModifiers", NBTType.Compound).addCompound();
attribute.setString("AttributeName", AttributeName.MOVEMENT_SPEED.getName());
attribute.setInt("Operation", AttributeOperation.ADD.getId());
attribute.setUUID("UUID", uuid);
attribute.setDouble("Amount", 0.1);
attribute.setString("Name", "movement_speed");
attribute.setString("Slot", "feet");
nItem.save();

Modern item merging

NItem.merge copies the source item's present components when their values differ from that source item's defaults. It discovers component types from the item itself, so newly added types do not require a manual merge list. Absent components, explicit removals and default values do not clear or reset values on the destination.

CUSTOM_DATA is merged recursively, including when the destination has none. Source values win on conflicting keys; copied NBT does not share mutable tags with the source item. ./gradlew check --no-daemon exercises this behavior against every configured modern adapter using its mapped Paper classes.

Limitations

Currently, supports only items.

Adding it to your project

Maven Central

Method 1 - Direct use on Spigot

This is the easiest way.

Step 1 - plugin.yml

name: Your Plugin
author: You
# ....
libraries:
  - beer.devs:FastNbt-jar:VERSION

Step 2 - Gradle Kotlin DSL

dependencies {
    compileOnly("beer.devs:FastNbt-jar:VERSION")
}

Method 2 - Direct use on Paper

This is the easiest way, but requires some special steps.

Step 1

Shade libby into your JAR, read more here.

Step 2 - plugin.yml

Add the lib into libraries-libby of your plugin.yml and specify the --remap flag.

name: Your Plugin
author: You
# ....
libraries-libby:
  - beer.devs:FastNbt-jar:VERSION --remap

Step 3

You need to include this LibsLoader class in your plugin and call in onLoad.
This will load the libraries you specified in the plugin.yml file.

new LibsLoader(this).loadAll();

Step 4 - Gradle Kotlin DSL

Same as Method 1

Method 3 - Shading

You can shade the library in your plugin if you want to use it without connecting to Maven Central.

Shading Configuration - Gradle Kotlin DSL

plugins {
    id("com.gradleup.shadow") version "9.4.1"
}

dependencies {
    implementation("beer.devs:FastNbt-jar:VERSION")
}

tasks.shadowJar {
    relocate("beer.devs.fastnbt", "YOUR_PACKAGE_HERE.libs.beer.devs.fastnbt")
}

Updating

Adapter layout

  • adapters/versions/<version>/adapter.properties declares the exact target's Paper dev bundle, Java release/toolchain, output mappings and source variant for each wrapper. This is the only adapter build configuration to maintain.
  • adapters/sources/<wrapper>/<wrapper>_<source-version>.java contains the canonical Java variants. Each target selects its variants explicitly; no version is selected by proximity or protocol number.
  • adapters/adapter.gradle.kts is the shared build script. Gradle Sync generates sources under each version's build/generated/sources/nms/main/java by replacing only the selected source version suffix in filenames and text.

Edit the canonical sources, not the generated files. A change to a shared variant affects every target selecting it. When behavior differs, add a new variant for that wrapper and select it only for the affected targets.

The Gradle project names remain :fastnbt_nms_<version>. Java package names, public APIs, Maven coordinates and the final output/FastNbt.jar are unchanged. Each target still compiles against its own dev bundle; legacy targets use reobf artifacts, while targets declaring mappings=mojang use their normal JAR.

Adding a version

  1. Add adapters/versions/<version>/adapter.properties, using an existing target as a starting point. Set devBundle, javaRelease, mappings (spigot or mojang) and, where required, javaToolchain.
  2. Select each wrapper's source version explicitly. CompoundTag, CraftItemStack, DataFixer, ListTag and NbtIo are required; DataComponents and NBTUtilsModern are included where needed.
  3. Add the version to the public Version enum, preserving the existing order. Settings and the final JAR discover the target from its manifest automatically.
  4. Run ./gradlew check :FastNbt-jar:shadowJar --no-daemon. The check compiles every adapter, runs the core tests and verifies that configured targets match supported enum entries, excluding documented historical versions and aliases.
  5. Verify new NMS behavior on Spigot and Paper, including NBT round trips and item copy/mirror semantics. Successful generation alone does not prove runtime compatibility.

LoneDev's Notes

Deploying to Maven Central

Create a Central Portal user token and put it in ~/.gradle/gradle.properties:

mavenCentralUsername=TOKEN_USERNAME
mavenCentralPassword=TOKEN_PASSWORD

The deploy uses the default GPG key through gpg-agent. To select one explicitly, add:

signing.gnupg.keyName=KEY_ID

Run .scripts/deploy_maven.sh or the Deploy to Maven Central IntelliJ run configuration. The deployment is validated and left for manual release at https://central.sonatype.com/publishing/deployments.

Editing to the repository

  • Clone it
  • Make your changes
  • Run ./gradlew build
  • Use the generated output/FastNbt.jar

Updating Javadocs

In order to update Javadocs you have to build locally, as old NMS jars are not available and can't be easily included on Github.

  • Run ./gradlew :FastNbt-core:javadoc
  • Get the generated Javadocs from FastNbt-core/build/docs/javadoc/
  • Push the contents into the javadoc branch

Solving java.lang.NoClassDefFoundError: ji$a and similar

This happens when mappings are out of date. Refresh Paperweight's dependencies and rebuild: ./gradlew build --refresh-dependencies

Used by

Contributors

Languages