Unreleased version v6.3.0-SNAPSHOT. This page describes the alpha branch and may change at any time; it is not part of any released version.

commit 49619c6 · 2026-09-13 12:21 UTC

Skip to content

Config Validation

Since v6.2.0

Config validation annotations are available starting from UltiTools-API v6.2.0.

UltiTools provides declarative validation annotations for configuration fields.

Refusal semantics as of v6.3.0

A config value that fails validation refuses the owning module at load, instead of being reset. See Behavior below.

Available Annotations

@Range

Validates that a numeric value falls within a specified range (inclusive).

java
package com.ultikits.docs.validation;

import com.ultikits.ultitools.abstracts.AbstractConfigEntity;
import com.ultikits.ultitools.annotations.ConfigEntity;
import com.ultikits.ultitools.annotations.ConfigEntry;
import com.ultikits.ultitools.annotations.config.*;
import lombok.Getter;
import lombok.Setter;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Getter
@Setter
@ConfigEntity("config/config.yml")
public class MyConfig extends AbstractConfigEntity {

    @Range(min = 1, max = 10)
    @ConfigEntry(path = "maxHomes", comment = "Maximum number of homes (1-10)")
    private int maxHomes = 5;

    @Range(min = 0.0, max = 100.0)
    @ConfigEntry(path = "taxRate", comment = "Tax rate percentage (0-100)")
    private double taxRate = 5.0;

    public MyConfig(String configFilePath) {
        super(configFilePath);
    }
}

If a server admin sets maxHomes: 999, the module is refused at load. The console error names the module, the config file, the field maxHomes, the actual value 999, and the violated constraint (@Range(min = 1, max = 10)); the file itself is left untouched.

AttributeTypeDefaultDescription
mindouble-Double.MAX_VALUEMinimum allowed value (inclusive)
maxdoubleDouble.MAX_VALUEMaximum allowed value (inclusive)

@NotEmpty

Validates that a String value is not null or empty (after trimming whitespace).

java
import com.ultikits.ultitools.annotations.config.NotEmpty;

@NotEmpty
@ConfigEntry(path = "serverName", comment = "Display name of the server")
private String serverName = "My Server";

A blank or missing value refuses the owning module at load; see Behavior below.

@Size

Validates that a Collection or String has a size/length within the specified bounds.

java
import com.ultikits.ultitools.annotations.config.Size;

@Size(min = 1, max = 50)
@ConfigEntry(path = "motd", comment = "Message of the day (1-50 characters)")
private String motd = "Welcome!";

@Size(min = 1, max = 10)
@ConfigEntry(path = "allowedWorlds", comment = "List of allowed worlds (1-10)")
private List<String> allowedWorlds = Arrays.asList("world", "world_nether");
AttributeTypeDefaultDescription
minint0Minimum size (inclusive)
maxintInteger.MAX_VALUEMaximum size (inclusive)

@Pattern

Validates that a String value matches a regular expression.

java
import com.ultikits.ultitools.annotations.config.Pattern;

@Pattern(regex = "^#[0-9A-Fa-f]{6}$")
@ConfigEntry(path = "chatColor", comment = "Chat color in hex format (#RRGGBB)")
private String chatColor = "#FFFFFF";

@Pattern(regex = "^[a-zA-Z0-9_]{3,16}$")
@ConfigEntry(path = "prefix", comment = "Prefix (alphanumeric, 3-16 chars)")
private String prefix = "Server";
AttributeTypeDefaultDescription
regexString(required)The regular expression pattern to match

Combining Annotations

You can use multiple validation annotations on the same field:

java
@NotEmpty
@Size(min = 3, max = 32)
@Pattern(regex = "^[a-zA-Z0-9_ ]+$")
@ConfigEntry(path = "displayName", comment = "Display name (3-32 alphanumeric chars)")
private String displayName = "Default Name";

Complete Example

java
package com.ultikits.docs.validation;

import com.ultikits.ultitools.abstracts.AbstractConfigEntity;
import com.ultikits.ultitools.annotations.ConfigEntity;
import com.ultikits.ultitools.annotations.ConfigEntry;
import com.ultikits.ultitools.annotations.config.*;
import lombok.Getter;
import lombok.Setter;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Getter
@Setter
@ConfigEntity("config/config.yml")
public class PluginConfig extends AbstractConfigEntity {

    @Range(min = 0, max = 300)
    @ConfigEntry(path = "teleport.warmup", comment = "Teleport warmup in seconds (0-300)")
    private int teleportWarmup = 3;

    @Range(min = 0, max = 3600)
    @ConfigEntry(path = "teleport.cooldown", comment = "Teleport cooldown in seconds (0-3600)")
    private int teleportCooldown = 60;

    @Range(min = 1, max = 100)
    @ConfigEntry(path = "home.maxHomes", comment = "Maximum homes per player (1-100)")
    private int maxHomes = 5;

    @NotEmpty
    @ConfigEntry(path = "messages.prefix", comment = "Chat prefix for plugin messages")
    private String messagePrefix = "[MyPlugin]";

    @Size(min = 1, max = 20)
    @ConfigEntry(path = "worlds.allowed", comment = "Worlds where the plugin is active")
    private List<String> allowedWorlds = Arrays.asList("world");

    @Pattern(regex = "^(DIAMOND|GOLD|IRON|STONE|WOOD)$")
    @ConfigEntry(path = "gui.borderItem", comment = "Border item material")
    private String borderItem = "DIAMOND";

    public PluginConfig(String configFilePath) {
        super(configFilePath);
    }
}

Behavior

Constructor resolution, as of v6.3.0

validateFields() obtains its default instance through the same two-step fallback ConfigManager uses elsewhere: a (String) constructor first, then a no-arg constructor calling super("config/path.yml"). Validation now fires on both shapes; only a class with neither constructor fails to register (see #314).

A config class registers successfully as soon as either constructor shape resolves — public MyConfig(String configFilePath) calling super(configFilePath), or a no-arg constructor calling super("config/path.yml") directly. Both are supported; declaring one is enough.

When a field's live value violates its constraint:

  1. The module is refused at load. The value is not reset, and the file is not rewritten. Other modules continue loading normally.
  2. The console error names the module, the config file, the field, the actual value, and the constraint that was violated, so the operator can fix the file without guessing.
  3. Nothing about the file itself changes. The value the operator wrote stays exactly as they wrote it until they edit it themselves.

This is different from a typo silently working around itself: a config file belongs to the server operator, and only the operator's own edit changes it. Restart the server, or reload the module, after correcting the value.

Contributors

No contributors

Released under the MIT License.