Explanation: JSON vs Hardcoded Content
Library of Exile supports two ways to define registry entries. Choosing between them is a deliberate design decision, not just a stylistic preference.
Hardcoded entries
A hardcoded entry is defined in Java and registered during FMLCommonSetupEvent. It exists for
the lifetime of the JVM and cannot be changed by a /reload.
When to use hardcoded:
- The entry has complex logic that cannot be expressed in JSON (a League with a custom
isInSide predicate, a MapDataBlock with custom generation code).
- The entry is a structural requirement — things like "empty" fallback entries that must always
exist before any JSON is parsed.
- The entry is internal to your mod and you deliberately do not want server admins to override it.
How to write:
Use ExileKey.ofId inside an ExileKeyHolder:
public ExileKey<MobList, KeyInfo> CAVE = ExileKey.ofId(
this, "my_mod:cave",
x -> new MobList(x.GUID(), 1000, entries, MobListTags.MAP)
);
Or use ExileRegistryEventClass for a batch:
public void init(ExileRegistryEvent e) {
e.add(new MyCaveMobList(), ExileRegistrationInfo.of("my_mod"));
}
JSON entries
A JSON entry lives in a datapack file under data/<namespace>/<type_id>/. It is parsed on
every /reload and fully replaces its previous version.
When to use JSON:
- The entry is pure data — weights, stat ranges, effect ids, item ids.
- You want server admins or modpack authors to be able to tune values without recompiling.
- The entry should participate in the Library of Exile datapack ecosystem (other mods can
override it with "loader": "REPLACE_FIELDS").
How to write:
Implement JsonExileRegistry<T> and IAutoGson<T> on your content class. Place JSON files
in the correct datapack folder. The library's BaseDataPackLoader handles everything else.
Mixed: addSeriazable
A third option exists: register an entry hardcoded but mark it as serializable. Use
e.addSeriazable(entry, info) instead of e.add(entry, info).
This means: - The entry is guaranteed to exist (registered in Java at startup). - A JSON file with the same GUID can partially or fully override it at runtime.
This is ideal for default values that most servers keep as-is, but that modpack authors occasionally need to adjust. For example, the built-in relic rarities (Common through Mythic) are registered this way — they have sensible defaults but can be overridden by a datapack without the mod author needing to change the code.
Overriding a addSeriazable entry from JSON:
{
"id": "library_of_exile:mythic",
"loader": "REPLACE_FIELDS",
"affixes": 4
}
Only affixes changes; all other fields keep their hardcoded values.
Client sync implications
Hardcoded entries are not included in ON_LOGIN sync packets. Only isFromDatapack() == true
entries (JSON entries and addSeriazable entries that were overridden by JSON) travel to the
client.
This means:
- If you want a hardcoded entry to be visible to clients, you must also register it client-side
— the client mod init code runs EXILE_REGISTRY_GATHER the same way the server does.
- If a JSON entry should be client-visible, use an ON_LOGIN type and it syncs automatically.
For most use cases, JSON entries for display data (relic stats, affix names) and hardcoded entries for logic-heavy types (leagues, custom map data blocks) is the natural split.
Summary
| Property | Hardcoded | JSON | addSeriazable |
|---|---|---|---|
| Defined in | Java | Datapack file | Java (overridable by JSON) |
| Hot-reloadable | No | Yes | Partially (JSON overlay only) |
| Overridable by server admin | No | Yes | Yes (via JSON) |
Included in ON_LOGIN sync |
No | Yes | Yes (if overridden) |
| Supports complex logic | Yes | No | No |
| Load order guaranteed | Yes | After FMLCommonSetup |
Yes (base); JSON part on reload |