Skip to content

Codecs

Codecs are Hytale’s serialization layer for reading and writing JSON (and BSON). They’re used everywhere: configuration files, asset definitions, entity persistence, and network packets.

CodecJava Type
Codec.STRINGString
Codec.BOOLEANboolean
Codec.BYTEbyte
Codec.SHORTshort
Codec.INTEGERint
Codec.LONGlong
Codec.FLOATfloat
Codec.DOUBLEdouble
CodecJava Type
Codec.STRING_ARRAYString[]
Codec.INT_ARRAYint[]
Codec.LONG_ARRAYlong[]
Codec.FLOAT_ARRAYfloat[]
Codec.DOUBLE_ARRAYdouble[]
Codec.BYTE_ARRAYbyte[]

These wrap primitive codecs with conversion functions:

CodecJSON TypeJava Type
Codec.PATHstringjava.nio.file.Path
Codec.UUID_STRINGstringUUID
Codec.UUID_BINARYbinaryUUID
Codec.INSTANTstringjava.time.Instant
Codec.DURATIONstringjava.time.Duration
Codec.DURATION_SECONDSnumberDuration (from seconds)
Codec.LOG_LEVELstringjava.util.logging.Level

KeyedCodec associates a codec with a JSON field name:

new KeyedCodec<>("MaxPlayers", Codec.INTEGER) // "MaxPlayers": 100
new KeyedCodec<>("Name", Codec.STRING) // "Name": "Alice"

Convention: JSON keys start with uppercase (MaxPlayers, not maxPlayers).

KeyedCodec is used with BuilderCodec to define class fields.

BuilderCodec creates codecs for Java classes using a fluent builder pattern. See Configuration for practical usage with mod configs.

public static final BuilderCodec<MyData> CODEC = BuilderCodec.builder(
MyData.class, MyData::new
)
.append(new KeyedCodec<>("Name", Codec.STRING),
(data, val) -> data.name = val, // setter
data -> data.name) // getter
.add()
.append(new KeyedCodec<>("Count", Codec.INTEGER),
(data, val) -> data.count = val,
data -> data.count)
.add()
.build();

Add .addValidator() between .append() and .add() to validate field values. See Configuration - Validation for common validators.

For class hierarchies, pass the parent codec:

// base class
public static final BuilderCodec<Animal> CODEC = BuilderCodec.abstractBuilder(Animal.class)
.append(new KeyedCodec<>("Name", Codec.STRING), ...)
.build();
// subclass inherits parent fields
public static final BuilderCodec<Dog> CODEC = BuilderCodec.builder(
Dog.class, Dog::new, Animal.CODEC)
.append(new KeyedCodec<>("Breed", Codec.STRING), ...)
.build();

Wrap an existing codec with conversion functions:

// string codec that converts to/from Path
Codec<Path> PATH_CODEC = new FunctionCodec<>(
Codec.STRING,
str -> Paths.get(str), // string to Path
path -> path.toString() // path to string
);

This is how Codec.PATH, Codec.UUID_STRING, and Codec.INSTANT are implemented.

For enum types:

public enum Priority { LOW, NORMAL, HIGH }
public static final Codec<Priority> CODEC = new EnumCodec<>(Priority.class);

JSON uses the enum name: "Priority": "HIGH"

For arrays of custom types:

// array of custom objects
Codec<MyItem[]> ITEMS_CODEC = new ArrayCodec<>(
MyItem.CODEC,
MyItem[]::new
);

For Map<String, V> types:

Codec<Map<String, Integer>> SCORES_CODEC = new MapCodec<>(
Codec.INTEGER,
HashMap::new
);

JSON:

{
"Scores": {
"Alice": 100,
"Bob": 85
}
}

CodecMapCodec handles polymorphic JSON where a "Type" field determines which concrete class to use.

public abstract class Reward {
// polymorphic codec - "Type" field selects concrete class
public static final CodecMapCodec<Reward> CODEC = new CodecMapCodec<>("Type");
// shared fields in abstract builder
public static final BuilderCodec<Reward> ABSTRACT_CODEC =
BuilderCodec.abstractBuilder(Reward.class)
.append(new KeyedCodec<>("Description", Codec.STRING), ...)
.build();
}
public class ItemReward extends Reward {
public static final BuilderCodec<ItemReward> CODEC = BuilderCodec.builder(
ItemReward.class, ItemReward::new, Reward.ABSTRACT_CODEC)
.append(new KeyedCodec<>("ItemId", Codec.STRING), ...)
.append(new KeyedCodec<>("Count", Codec.INTEGER), ...)
.build();
}
public class XpReward extends Reward {
public static final BuilderCodec<XpReward> CODEC = BuilderCodec.builder(
XpReward.class, XpReward::new, Reward.ABSTRACT_CODEC)
.append(new KeyedCodec<>("Amount", Codec.INTEGER), ...)
.build();
}

Register concrete types during setup():

@Override
protected void setup() {
// direct registration on the codec
Reward.CODEC.register("Item", ItemReward.class, ItemReward.CODEC);
Reward.CODEC.register("Xp", XpReward.class, XpReward.CODEC);
// or via registry (auto-cleanup on mod shutdown)
this.getCodecRegistry(Reward.CODEC)
.register(Priority.NORMAL, "Item", ItemReward.class, ItemReward.CODEC);
}

Once registered, JSON can use your types:

{
"Type": "Item",
"Description": "Starter sword",
"ItemId": "iron_sword",
"Count": 1
}
{
"Type": "Xp",
"Description": "Quest completion bonus",
"Amount": 500
}

You can add new subtypes to existing Hytale systems:

// add custom interaction type
this.getCodecRegistry(Interaction.CODEC)
.register(Priority.NORMAL, "MyCustomEffect", MyCustomEffect.class, MyCustomEffect.CODEC);

Now your types can be used in JSON asset files alongside built-in types.