Skip to content

Serialization (Codec) System Overview

The Codec System provides serialization and deserialization for BSON and JSON formats. It uses a type-safe, builder-based approach with support for versioning, validation, and polymorphism.

com.hypixel.hytale.codec

  • Core: codec/
  • Builders: codec/builder/
  • Built-in Codecs: codec/codecs/
  • Validation: codec/validation/
┌─────────────────────────────────────────────────────────────────┐
│ Codec System │
│ │
│ ┌──────────────────┐ ┌───────────────────────────────┐ │
│ │ Codec<T> │ │ BuilderCodec<T> │ │
│ │ (Interface) │◄───────│ (Complex Objects) │ │
│ └────────┬─────────┘ └───────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ Built-in Codecs │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────────────┐ │ │
│ │ │ String │ │ Integer │ │ Array │ │ Map │ │ │
│ │ │ Codec │ │ Codec │ │ Codec │ │ Codec │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────────────┘ │ │
│ └────────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────────┐ │
│ │ KeyedCodec<T> │ │
│ │ (Field with name + codec) │ │
│ └────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘

The main serialization interface:

package com.hypixel.hytale.codec;
public interface Codec<T> extends RawJsonCodec<T>, SchemaConvertable<T> {
// Decode from BSON
T decode(BsonValue bsonValue, ExtraInfo extraInfo);
// Encode to BSON
BsonValue encode(T value, ExtraInfo extraInfo);
// Decode from JSON
T decodeJson(RawJsonReader reader, ExtraInfo extraInfo) throws IOException;
// Generate JSON schema
Schema toSchema(SchemaContext context);
}

Wraps a codec with a field name:

package com.hypixel.hytale.codec;
public class KeyedCodec<T> {
public KeyedCodec(String key, Codec<T> codec);
// Get value from document (throws if missing)
public T get(BsonDocument doc, ExtraInfo info);
// Get value or null if missing
public T getOrNull(BsonDocument doc, ExtraInfo info);
// Get with default value
public T getOrDefault(BsonDocument doc, ExtraInfo info, T defaultValue);
// Put value into document
public void put(BsonDocument doc, T value, ExtraInfo info);
}

Key names must start with an uppercase letter.

CodecJava TypeUsage
Codec.STRINGStringText values
Codec.BOOLEANBooleanTrue/false
Codec.INTEGERInteger32-bit integers
Codec.LONGLong64-bit integers
Codec.FLOATFloat32-bit floats
Codec.DOUBLEDouble64-bit doubles
Codec.BYTEByte8-bit integers
Codec.SHORTShort16-bit integers
CodecJava Type
Codec.STRING_ARRAYString[]
Codec.INT_ARRAYint[]
Codec.LONG_ARRAYlong[]
Codec.FLOAT_ARRAYfloat[]
Codec.DOUBLE_ARRAYdouble[]
CodecJava TypeDescription
Codec.UUID_BINARYUUIDBinary UUID format
Codec.UUID_STRINGUUIDString UUID format
Codec.PATHPathFile path
Codec.INSTANTInstantTimestamp
Codec.DURATIONDurationTime duration (string)
Codec.DURATION_SECONDSDurationTime duration (seconds)
// Map with string keys
new MapCodec<>(valueCodec, HashMap::new);
// Array of objects
new ArrayCodec<>(elementCodec, defaultArray);
// Set of values
new SetCodec<>(valueCodec, HashSet::new);
// Enum values
new EnumCodec<>(MyEnum.class);

For complex object serialization:

package com.hypixel.hytale.codec.builder;
public class BuilderCodec<T> implements Codec<T> {
// Create builder for concrete class
public static <T> BuilderCodecBuilder<T> builder(
Class<T> clazz,
Supplier<T> constructor
);
// Create builder with parent codec (inheritance)
public static <T> BuilderCodecBuilder<T> builder(
Class<T> clazz,
Supplier<T> constructor,
BuilderCodec<? super T> parent
);
// Create builder for abstract class
public static <T> BuilderCodecBuilder<T> abstractBuilder(Class<T> clazz);
}

Fluent API for defining codecs:

BuilderCodec.builder(MyClass.class, MyClass::new)
// Add field
.append(
new KeyedCodec<>("FieldName", Codec.STRING),
(obj, value) -> obj.field = value, // Setter
obj -> obj.field // Getter
)
.documentation("Field description")
.addValidator(Validators.nonNull())
.add()
// Add more fields...
// Enable versioning
.versioned()
.codecVersion(1)
// Build final codec
.build();
import com.hypixel.hytale.codec.Codec;
import com.hypixel.hytale.codec.KeyedCodec;
import com.hypixel.hytale.codec.builder.BuilderCodec;
import com.hypixel.hytale.codec.validation.Validators;
public class MyConfig {
public String name;
public float value;
public boolean enabled;
public static final BuilderCodec<MyConfig> CODEC =
BuilderCodec.builder(MyConfig.class, MyConfig::new)
.append(
new KeyedCodec<>("Name", Codec.STRING),
(config, name) -> config.name = name,
config -> config.name
)
.addValidator(Validators.nonNull())
.documentation("Configuration name")
.add()
.append(
new KeyedCodec<>("Value", Codec.FLOAT),
(config, value) -> config.value = value,
config -> config.value
)
.documentation("Numeric value")
.add()
.append(
new KeyedCodec<>("Enabled", Codec.BOOLEAN),
(config, enabled) -> config.enabled = enabled,
config -> config.enabled
)
.add()
.build();
}
public abstract class BaseConfig {
public String id;
public static final BuilderCodec<BaseConfig> BASE_CODEC =
BuilderCodec.abstractBuilder(BaseConfig.class)
.appendInherited(
new KeyedCodec<>("Id", Codec.STRING),
(obj, id) -> obj.id = id,
obj -> obj.id,
(obj, parent) -> obj.id = parent.id
)
.addValidator(Validators.nonNull())
.add()
.build();
}
public class DerivedConfig extends BaseConfig {
public int level;
public static final BuilderCodec<DerivedConfig> CODEC =
BuilderCodec.builder(DerivedConfig.class, DerivedConfig::new,
BaseConfig.BASE_CODEC)
.append(
new KeyedCodec<>("Level", Codec.INTEGER),
(obj, level) -> obj.level = level,
obj -> obj.level
)
.add()
.build();
}
import com.hypixel.hytale.codec.polymorphic.CodecMapCodec;
public abstract class ActionConfig {
// Polymorphic dispatch by "Type" field
public static final CodecMapCodec<ActionConfig> CODEC =
new CodecMapCodec<>("Type");
public static final BuilderCodec<ActionConfig> BASE_CODEC =
BuilderCodec.abstractBuilder(ActionConfig.class)
.build();
}
public class DamageActionConfig extends ActionConfig {
public float damage;
public static final BuilderCodec<DamageActionConfig> CODEC =
BuilderCodec.builder(DamageActionConfig.class, DamageActionConfig::new,
ActionConfig.BASE_CODEC)
.append(
new KeyedCodec<>("Damage", Codec.FLOAT),
(obj, damage) -> obj.damage = damage,
obj -> obj.damage
)
.add()
.build();
// Register this type
static {
ActionConfig.CODEC.register("Damage", DamageActionConfig.class,
DamageActionConfig.CODEC);
}
}

JSON usage:

{
"Type": "Damage",
"Damage": 10.0
}

Built-in validators:

import com.hypixel.hytale.codec.validation.Validators;
// Non-null validation
.addValidator(Validators.nonNull())
// Range validation
.addValidator(Validators.range(0.0, 100.0))
// Greater than
.addValidator(Validators.greaterThan(0))
// Less than
.addValidator(Validators.lessThan(1000))
// Custom validation
.addValidator((value, info) -> {
if (!isValid(value)) {
throw new ValidationException("Invalid value");
}
})

Support for schema evolution:

BuilderCodec.builder(MyClass.class, MyClass::new)
// Field added in version 1
.append(new KeyedCodec<>("OldField", Codec.STRING), ...)
.codecVersion(1, 2) // Versions 1-2
.add()
// Field added in version 2
.append(new KeyedCodec<>("NewField", Codec.INTEGER), ...)
.codecVersion(2) // Version 2+
.add()
.versioned()
.build();

Context passed during serialization:

package com.hypixel.hytale.codec;
public class ExtraInfo {
// Get thread-local instance
public static ExtraInfo get();
// Current decode path
public List<String> getPath();
// Add validation error
public void addError(String message);
// Check for errors
public boolean hasErrors();
}
import org.bson.BsonValue;
import com.hypixel.hytale.codec.ExtraInfo;
MyConfig config = new MyConfig();
config.name = "Test";
config.value = 42.0f;
config.enabled = true;
BsonValue bson = MyConfig.CODEC.encode(config, ExtraInfo.get());
import org.bson.BsonDocument;
import com.hypixel.hytale.codec.ExtraInfo;
BsonDocument doc = BsonDocument.parse("{\"Name\": \"Test\", \"Value\": 42.0}");
MyConfig config = MyConfig.CODEC.decode(doc, ExtraInfo.get());
import com.hypixel.hytale.codec.json.RawJsonReader;
import java.io.StringReader;
String json = "{\"Name\": \"Test\", \"Value\": 42.0}";
RawJsonReader reader = new RawJsonReader(new StringReader(json));
MyConfig config = MyConfig.CODEC.decodeJson(reader, ExtraInfo.get());
  1. Use KeyedCodec: Always use uppercase field names
  2. Add validators: Validate required fields with Validators.nonNull()
  3. Document fields: Use .documentation() for clarity
  4. Version your codecs: Enable versioning for evolution
  5. Register polymorphic types: Use CodecMapCodec for type dispatch
  6. Reuse codecs: Store codecs as static final fields