Skip to content

Constraints and Metadata

GMalvestiti edited this page Aug 31, 2026 · 1 revision

Constraint annotations state the rules a field's value must satisfy. Lite Config enforces them on every load and update and exposes them through config metadata.

@Config(name = "mymod")
public final class MyModConfig {

    @Entry(comment = "Scale of the HUD overlay.")
    @Range(min = 1, max = 8)
    public int hudScale = 2;

    @Pattern("[a-z0-9_]+")
    @Length(max = 16)
    public String profileName = "default";

    @Length(min = 1, max = 4)
    public List<String> enabledFeatures = new ArrayList<>(List.of("hud"));

    public RenderMode mode = RenderMode.BALANCED;
}

The annotations

@Range(min, max) bounds a numeric field. Both attributes are optional, so @Range(min = 0) leaves the upper end open. The bounds are inclusive.

@Pattern(regex) requires a string field to match the expression in full.

@Length(min, max) bounds the size of a string, collection, map, or array.

An enum field needs no annotation — its constants already are the set of values it accepts.

A constraint must match the field type: @Range supports numeric fields, @Pattern supports strings, and @Length supports strings, collections, maps, and arrays. A mismatched annotation, a range with a NaN bound, a min above its max, or an expression that does not compile is a mistake in the config class rather than in the file. It is reported with INVALID_CONSTRAINT when the holder is built.

What enforcement looks like

A violated rule behaves exactly like one raised from validate: the update is rejected as a whole and the reason comes back in the result.

UpdateResult result = holder.update(config -> config.hudScale = 12);
result.accepted();                       // false
result.violations().getFirst().id();     // "range.hudScale"

Violation ids follow the rule that produced them — range., pattern., length., or value. for an enum the file names incorrectly — followed by the path of the field in the file. A field inside a nested object uses its full path, so section.max_depth reads the same way in the violation as it does in the file.

Constraints run before validate, so a hook of your own only sees candidates that already satisfy the declared rules. NaN is rejected whenever @Range is present, including an otherwise open-ended range.

File comments

Config files render only comments explicitly declared with @Config(comment = ...) and @Entry(comment = ...). Defaults, constraints, restart requirements, and migration versions remain available through metadata but do not generate file comments.

Reading the metadata

ConfigHolder#metadata() returns everything the annotations say, which is what a settings screen, a command, or a documentation generator needs.

ConfigMetadata metadata = holder.metadata();

ConfigProperty hudScale = metadata.property("hudScale").orElseThrow();
hudScale.defaultValue();                 // 2
hudScale.constraints().max();            // OptionalDouble[8.0]
hudScale.translationKey();               // Optional.empty()

metadata.flatten().forEach(property ->
    System.out.println(property.path() + " -> " + property.type().getSimpleName()));

property(path) takes the same dotted path the file uses. flatten() walks the whole tree, nested objects included; properties() stays at the top level and lets you descend through children() yourself.

Metadata describes the config; it never touches the live state. Collection and map defaults are immutable snapshots, and array defaults are copied on access. A mutable value handled by a custom codec should still be treated as read-only and copied before editing. Change live values through ConfigHolder#update or updateAndSave.

Translation keys

@Entry(translationKey = ...) records a field label for settings screens and other integrations. Lite Config exposes the key through metadata but does not resolve it. See Field Entries for an example.

Clone this wiki locally